Connecting to Azure IoT Hub with Go and MQTT

In this article, I want to show you how to connect a Go application to Azure IoT Hub using the MQTT protocol. We'll implement a secure connection with TLS (Transport Layer Security) to encrypt our data and use SAS (Shared Access Signature) tokens for authentication.
I assume you already have an Azure IoT Hub with at least one device added to it. You'll need three pieces of information to use this code:
Your IoT Hub name
Your device ID
Your device key (the primary key that was automatically generated when you added the device)
For implementing MQTT connectivity, we'll use the Eclipse Paho MQTT Go client library.
Generating SAS (Shared Access Signature) Token
The generateSASToken
function is a crucial part of our implementation. It creates a secure authentication token that Azure IoT Hub will recognize and accept. The function takes four inputs: hub name, device ID, device key, and an expiration duration.
The function works by creating a unique signature from the resource URI (combining hub name and device ID) and a timestamp. This signature is then hashed using HMAC-SHA256 with the device key. The resulting token allows our device to authenticate with Azure IoT Hub while ensuring the connection remains secure for the specified duration.
func generateSASToken(hubName, deviceID, deviceKey string, expiration time.Duration) string {
// create the timestamp for expiration
timestamp := time.Now().Add(expiration).Unix()
timestampString := strconv.FormatInt(timestamp, 10)
// create the resource uri
uri := hubName + "/devices/" + deviceID
escapedURI := url.QueryEscape(uri)
// create the signature
signature := escapedURI + "\n" + timestampString
// hash the signature
decodedKey, _ := base64.StdEncoding.DecodeString(deviceKey)
hash := hmac.New(sha256.New, decodedKey)
hash.Write([]byte(signature))
encryptedSignature := hash.Sum(nil)
decodedSignature := base64.StdEncoding.EncodeToString(encryptedSignature)
escapedSignature := url.QueryEscape(decodedSignature)
// create and return the token
return "SharedAccessSignature sr=" + escapedURI + "&sig=" + escapedSignature + "&se=" + timestampString
}
Configuring and Creating the MQTT Client
In this configuration, we're establishing a secure connection to Azure IoT Hub using TLS over port 8883. The broker URL is formatted with the "ssl://" prefix to indicate secure communication. For authentication, Azure IoT Hub requires a specific username format combining the hub name and device ID with a slash separator (hubName + "/" + deviceID
), while the SAS token we generated serves as the password. The TLS configuration is set to use the system's trusted certificates to verify Azure's server identity, with InsecureSkipVerify set to false to ensure proper certificate validation.
// create the client options
brokerURL := "ssl://" + hubName + ":8883"
username := hubName + "/" + deviceID
sasToken := generateSASToken(hubName, deviceID, deviceKey, tokenExpiration)
certPool, err := x509.SystemCertPool()
if err != nil {
slog.Error("failed to load system certificate pool", "error", err)
return
}
tlsConfig := &tls.Config{
RootCAs: certPool,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: false,
ServerName: hubName,
}
opts := mqtt.NewClientOptions().
AddBroker(brokerURL).
SetClientID(deviceID).
SetUsername(username).
SetPassword(sasToken).
SetTLSConfig(tlsConfig)
// create the client
client := mqtt.NewClient(opts)
Connecting to Azure IoT Hub
After configuring our client options, the next step is to establish the connection with Azure IoT Hub's MQTT broker. Here, we initiate the connection with client.Connect()
, which returns a token that we can use to track the operation's status. The WaitTimeout()
method blocks execution until the connection is established or the specified timeout is reached. We then check for any errors that might have occurred during the connection process. The defer
statement ensures our client disconnects properly when the function exits, even if an error occurs later. Upon successful connection, we log a message to confirm we've connected to the broker.
// connect to the broker
token := client.Connect()
token.WaitTimeout(connectTimeout)
if err := token.Error(); err != nil {
slog.Error("failed to connect", "error", err)
return
}
defer client.Disconnect(disconnectTimeout)
slog.Info("connected")
Publishing Messages to Azure IoT Hub
Once connected, we can publish messages to Azure IoT Hub using the appropriate topic format. For device-to-cloud messaging, Azure IoT Hub expects messages on the topic devices/{deviceId}/messages/events/
. The Publish method requires several parameters: the topic, Quality of Service (QoS) level, retain flag, and the message payload. For our example, we use QoS level 1, which ensures at-least-once delivery according to the MQTT specification (IBM documentation on QoS). The retain flag is set to true, indicating that the broker should keep the message for future subscribers. We use a simple "hello" string as our payload, which is converted to bytes before transmission. Similar to the connection process, we wait for the publish operation to complete or time out, and then check for any errors. A success message is logged when the message is successfully published to Azure IoT Hub.
// publish the message
topic := "devices/" + deviceID + "/messages/events/"
payload := "hello"
token = client.Publish(
topic, // topic
1, // qos
true, // retain
[]byte(payload), //payload
)
token.WaitTimeout(publishTimeout)
if err := token.Error(); err != nil {
slog.Error("failed to publish", "error", err)
return
}
slog.Info("message published")
Complete Implementation
Below is the complete Go code for connecting to Azure IoT Hub using MQTT with TLS security and SAS token authentication. This implementation includes all the components we've discussed: configuration, SAS token generation, client setup, connection establishment, and message publishing. Simply replace the placeholder values with your actual Azure IoT Hub information to get started.
package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"log/slog"
"net/url"
"strconv"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
// configuration
const (
hubName = "your-hub-name.azure-devices.net"
deviceID = "your-device-id"
deviceKey = "your-device-key"
tokenExpiration = 60 * time.Minute
)
// timeouts
const (
connectTimeout = 5 * time.Second
disconnectTimeout = 3000 // milliseconds
publishTimeout = 5 * time.Second
)
func main() {
// create the client options
brokerURL := "ssl://" + hubName + ":8883"
username := hubName + "/" + deviceID
sasToken := generateSASToken(hubName, deviceID, deviceKey, tokenExpiration)
certPool, err := x509.SystemCertPool()
if err != nil {
slog.Error("failed to load system certificate pool", "error", err)
return
}
tlsConfig := &tls.Config{
RootCAs: certPool,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: false,
ServerName: hubName,
}
opts := mqtt.NewClientOptions().
AddBroker(brokerURL).
SetClientID(deviceID).
SetUsername(username).
SetPassword(sasToken).
SetTLSConfig(tlsConfig)
// create the client
client := mqtt.NewClient(opts)
// connect to the broker
token := client.Connect()
token.WaitTimeout(connectTimeout)
if err := token.Error(); err != nil {
slog.Error("failed to connect", "error", err)
return
}
defer client.Disconnect(disconnectTimeout)
slog.Info("connected")
// publish the message
topic := "devices/" + deviceID + "/messages/events/"
payload := "hello"
token = client.Publish(
topic, // topic
1, // qos
true, // retain
[]byte(payload), //payload
)
token.WaitTimeout(publishTimeout)
if err := token.Error(); err != nil {
slog.Error("failed to publish", "error", err)
return
}
slog.Info("message published")
}
func generateSASToken(hubName, deviceID, key string, expiration time.Duration) string {
// create the timestamp for expiration
timestamp := time.Now().Add(expiration).Unix()
timestampString := strconv.FormatInt(timestamp, 10)
// create the resource uri
uri := hubName + "/devices/" + deviceID
escapedURI := url.QueryEscape(uri)
// create the signature
signature := escapedURI + "\n" + timestampString
// hash the signature
decodedKey, _ := base64.StdEncoding.DecodeString(key)
hash := hmac.New(sha256.New, decodedKey)
hash.Write([]byte(signature))
encryptedSignature := hash.Sum(nil)
decodedSignature := base64.StdEncoding.EncodeToString(encryptedSignature)
escapedSignature := url.QueryEscape(decodedSignature)
// create and return the token
return "SharedAccessSignature sr=" + escapedURI + "&sig=" + escapedSignature + "&se=" + timestampString
}
Final Thoughts
This implementation focuses on the essentials of connecting to Azure IoT Hub via MQTT. I personally couldn't find any straightforward Go examples for this specific scenario online, which made creating this guide a priority.
The code covers the basics of authentication and message publishing, though there's room for improvement. For production use, you'd want to add proper connection event handlers, implement automatic SAS token renewal, and further customize the TLS configuration.
I hope this example provides a useful starting point for your IoT projects with Go and Azure. While simplified, it contains the key elements needed to establish secure communication with your IoT Hub.
Subscribe to my newsletter
Read articles from Erman İmer directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
