Using JSON Web Tokens (JWTs) | Omnicore Documentation (2024)

To authenticate to Omnicore, each device must prepare a JSON Web Token (JWT, RFC 7519). JWTs are used for short-lived authentication between devices and the MQTT or HTTP bridges. This page describes the Omnicore requirements for the contents of the JWT.

Omnicore does not require a specific token generation method. A good collection of helper client libraries can be found on JWT.io.

When creating an MQTT client, the JWT must be passed in the password field of the CONNECT message. When connecting over HTTP, a JWT must be included as bearer token in the 'Authorization' header of each HTTP request.

Creating JWTs

JWTs are composed of three sections: a header, a payload (containing a claim set), and a signature. The header and payload are JSON objects, which are serialized to UTF-8 bytes, then encoded using base64url encoding.

The JWT's header, payload, and signature are concatenated with periods (.). As a result, a JWT typically takes the following form:

{Base64url encoded header}.{Base64url encoded payload}.{Base64url encoded signature}

The following sample illustrates how to create a Omnicore JWT . After creating the JWT, you can connect to the MQTT or HTTP bridge to publish messages from a device.The steps for configuring the client ID and authenticating a device.

/** * Calculates issued at / expiration times for JWT and places the time, as a * Unix timestamp, in the strings passed to the function. The time_size * parameter specifies the length of the string allocated for both iat and exp. */static void GetIatExp(char* iat, char* exp, int time_size) { // TODO(#72): Use time.google.com for iat time_t now_seconds = time(NULL); snprintf(iat, time_size, "%lu", now_seconds); snprintf(exp, time_size, "%lu", now_seconds + 3600); if (TRACE) { printf("IAT: %s\n", iat); printf("EXP: %s\n", exp); }}static int GetAlgorithmFromString(const char* algorithm) { if (strcmp(algorithm, "RS256") == 0) { return JWT_ALG_RS256; } if (strcmp(algorithm, "ES256") == 0) { return JWT_ALG_ES256; } return -1;}/** * Calculates a JSON Web Token (JWT) given the path to a EC private key . * Returns the JWT as a string that the caller must * free. */static char* CreateJwt(const char* ec_private_path, const char* algorithm) { char iat_time[sizeof(time_t) * 3 + 2]; char exp_time[sizeof(time_t) * 3 + 2]; uint8_t* key = NULL; // Stores the Base64 encoded certificate size_t key_len = 0; jwt_t* jwt = NULL; int ret = 0; char* out = NULL; // Read private key from file FILE* fp = fopen(ec_private_path, "r"); if (fp == NULL) { printf("Could not open file: %s\n", ec_private_path); return ""; } fseek(fp, 0L, SEEK_END); key_len = ftell(fp); fseek(fp, 0L, SEEK_SET); key = malloc(sizeof(uint8_t) * (key_len + 1)); // certificate length + \0 fread(key, 1, key_len, fp); key[key_len] = '\0'; fclose(fp); // Get JWT parts GetIatExp(iat_time, exp_time, sizeof(iat_time)); jwt_new(&jwt); // Write JWT ret = jwt_add_grant(jwt, "iat", iat_time); if (ret) { printf("Error setting issue timestamp: %d\n", ret); } ret = jwt_add_grant(jwt, "exp", exp_time); if (ret) { printf("Error setting expiration: %d\n", ret); } ret = jwt_set_alg(jwt, GetAlgorithmFromString(algorithm), key, key_len); if (ret) { printf("Error during set alg: %d\n", ret); } out = jwt_encode_str(jwt); if (!out) { perror("Error during token creation:"); } // Print JWT if (TRACE) { printf("JWT: [%s]\n", out); } jwt_free(jwt); free(key); return out;}
static MqttCallback mCallback;static long MINUTES_PER_HOUR = 60;/** Create a Omnicore JWT signed with the given RSA key. */private static String createJwtRsa( String privateKeyFile) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { DateTime now = new DateTime(); // Create a JWT to authenticate this device. The device will be disconnected after the token // expires, and will have to reconnect with a new token.  JwtBuilder jwtBuilder = Jwts.builder() .setIssuedAt(now.toDate()) .setExpiration(now.plusMinutes(20).toDate()) byte[] keyBytes = Files.readAllBytes(Paths.get(privateKeyFile)); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();}
import ( "errors" "io/ioutil" "time" jwt "github.com/golang-jwt/jwt")// createJWT creates a Omnicore JWT for the given subscription id.// algorithm can be one of ["RS256", "ES256"].func createJWT( privateKeyPath string, algorithm string, expiration time.Duration) (string, error) { claims := jwt.StandardClaims{ IssuedAt: time.Now().Unix(), ExpiresAt: time.Now().Add(expiration).Unix(), } keyBytes, err := ioutil.ReadFile(privateKeyPath) if err != nil { return "", err } token := jwt.NewWithClaims(jwt.GetSigningMethod(algorithm), claims) switch algorithm { case "RS256": privKey, _ := jwt.ParseRSAPrivateKeyFromPEM(keyBytes) return token.SignedString(privKey) case "ES256": privKey, _ := jwt.ParseECPrivateKeyFromPEM(keyBytes) return token.SignedString(privKey) } return "", errors.New("Cannot find JWT algorithm. Specify 'ES256' or 'RS256'")}
const createJwt = ( privateKeyFile, algorithm) => { // Create a JWT to authenticate this device. The device will be disconnected // after the token expires, and will have to reconnect with a new token. The const token = { iat: parseInt(Date.now() / 1000), exp: parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes }; const privateKey = readFileSync(privateKeyFile); return jwt.sign(token, privateKey, {algorithm: algorithm});};
def create_jwt( private_key_file, algorithm): """Creates a JWT (https://jwt.io) to establish an MQTT connection. Args: private_key_file: A path to a file containing either an RSA256 or ES256 private key. algorithm: The encryption algorithm to use. Either 'RS256' or 'ES256' Returns: A JWT generated from the given private key, which expires in 20 minutes. After 20 minutes, your client will be disconnected, and a new JWT will have to be generated. Raises: ValueError: If the private_key_file does not contain a known key. """ token = { # The time that the token was issued at "iat": datetime.datetime.now(tz=datetime.timezone.utc), # The time the token expires. "exp": datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(minutes=20), } # Read the private key file. with open(private_key_file, "r") as f: private_key = f.read() print( "Creating JWT using {} from private key file {}".format( algorithm, private_key_file ) ) return jwt.encode(token, private_key, algorithm=algorithm)

JWT header

The JWT header consists of two fields that indicate the signing algorithm and the type of token. Both fields are mandatory, and each field has only one value. Omnicore supports the following signing algorithms:

  • JWT RS256 (RSASSA-PKCS1-v1_5 using SHA-256 RFC 7518 sec 3.3). This is expressed as RS256 in the alg field in the JWT header.

  • JWT ES256 (ECDSA using P-256 and SHA-256 RFC 7518 sec 3.4), defined in OpenSSL as the prime256v1 curve. This is expressed as ES256 in the alg field in the JWT header.

In addition to the signing algorithm, you must supply the JWT token format.

The JSON representation of the header is as follows:

For RSA keys:

{ "alg": "RS256", "typ": "JWT" }

For Elliptic Curve keys:

{ "alg": "ES256", "typ": "JWT" }

The algorithm specified in the header must match at least one of the public keys registered for the device.

NOTE

The JWT header is not the same as the HTTP header (if you're connecting over HTTP).

JWT claims

The JWT payload contains a set of claims, and it is signed using the asymmetric keys. The JWT claim set contains information about the JWT, such as the target of the token, the issuer, the time the token was issued, and/or the lifetime of the token. Like the JWT header, the JWT claim set is a JSON object and is used in the calculation of the signature.

Required claims

Omnicore requires the following reserved claim fields. They may appear in any order in the claim set.

The nbf("Not Before") claim will be ignored, and is not required.

A JSON representation of the required reserved fields in a Omnicore JWT claim set is shown below:

For Elliptic Curve keys:

{ "iat": 1509654401, "exp": 1612893233}

JWT signature

The JSON Web Signature (JWS) specification guides the mechanics of generating the signature for the JWT. The input for the signature is the byte array of the following content:

{Base64url encoded header}.{Base64url encoded claim set}

To compute the signature, sign the base64url-encoded header, base64-url encoded claim set, and a secret key (such as an rsa_private.pem file) using the algorithm you defined in the header. The signature is then base64url-encoded, and the result is the JWT. The following example shows a JWT before base64url encoding:

{"alg": "RS256", "typ": "JWT"}.{ "iat": 1509654401, "exp": 1612893233}.[signature bytes]

After the final encoding, the JWT looks like the following:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjMxMDcwMjE5MDcsImlhdCI6MTY3MzU5Nzk0NH0.DNPWPXg8whGF66gycOJwnGcGv4bywtSt7GZEgeHrdrG_qJfIBNaYeeM1ElCy9bz9zw5X6qrXg-xdUsPTMgHCID3kiaZOu7yzdg4KXWIWQGAeWPUmeNXuXopvEvPu-398VDBuqXINTgf9O3WUBdzxHCW2iVOIJKvq7xybMZhcJmt_LEqlwGAM-xwE2-MSrnhnseLRkpIL_PH3YcHkfeb-0961XROFr-f5y3WEy8cyObt67iB_bO_QgShf0HQZwD6GFq-00D_HN7wdGYF4ruokV0SGLl-I7TkSqGdVbtLmDx38vXtF_S3ANegVNsu4pusvIHzXAcQ6MjOCuoYKNi8WjA
Using JSON Web Tokens (JWTs) | Omnicore Documentation (2024)
Top Articles
Latest Posts
Article information

Author: Mr. See Jast

Last Updated:

Views: 5875

Rating: 4.4 / 5 (75 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Mr. See Jast

Birthday: 1999-07-30

Address: 8409 Megan Mountain, New Mathew, MT 44997-8193

Phone: +5023589614038

Job: Chief Executive

Hobby: Leather crafting, Flag Football, Candle making, Flying, Poi, Gunsmithing, Swimming

Introduction: My name is Mr. See Jast, I am a open, jolly, gorgeous, courageous, inexpensive, friendly, homely person who loves writing and wants to share my knowledge and understanding with you.