#JWT authentication
The JSON Web Token mechanism is used for securely communicating with Setu. It is an open standard for representing claims securely between two parties.
The JWT website ↗ covers the basics of how it works and the concepts involved. This guide provides a simple description of how JWT is implemented at Setu.
Please note, while Setu supports JWT authentication, features like generating keys, using one key to access multiple configurations etc, will not be supported for JWT keys. Instead, use the more secure OAuth API keys ↗ to access these features.
Calling Setu-hosted APIs
A partner needs to authenticate API requests made to Setu. One supported method for authenticating is to use JWT.
For Setu's products, JWT keys have a one-to-one mapping with individual product configurations. Essentially, each of your configurations come with their own JWT key that you can use to authorise API requests.
If you are an Admin for your Bridge account, you should be able to see the API keys card under “Org settings” in the left panel. Click the JWT keys card to view keys for all your product configs. Read more ↗.
Read more below to understand the JWT format supported by Setu.
#JWT basics
The JWT is sent as part of the authorisation header of every API request. Typically it looks like this—
// Auth header formatAuthorization: Bearer <JWT_TOKEN>// Sample JWT encodedAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiI4ZjQ3ZTQxYy00Njg0LTRhZjEtYjFiOC0yNDZjODMxMTIwMzMiLCJpYXQiOjE1NzI0OTMzNTYsImp0aSI6ImM1MzQ0N2UzLTk5M2EtNDRhZS05Yzc4LTMxZjQ1YzNjMDgyZSJ9.xfYii9lfWMHtMBgjK5U6NFN_6_Q9jw8UMQu8Jnvftbs
The JWT in itself has three parts—A header
, payload
, and a signature
. Setu uses the following format—
// JWT formaturlsafe_base64(<JWT_HEADER>).urlsafe_base64(<JWT_PAYLOAD>).urlsafe_base64(<SIGNATURE>)// Sample JWT decoded{"alg" : "HS256","typ" : "JWT"},{"aud" : "8f47e41c-4684-4af1-b1b8-246c83112033", // This is the schemeID"iat" : 1572493356,"jti" : "c53447e3-993a-44ae-9c78-31f45c3c082e"}
This means that—
header : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9payload : eyJhdWQiOiI4ZjQ3ZTQxYy00Njg0LTRhZjEtYjFiOC0yNDZjODMxMTIwMzMiLCJpYXQiOjE1NzI0OTMzNTYsImp0aSI6ImM1MzQ0N2UzLTk5M2EtNDRhZS05Yzc4LTMxZjQ1YzNjMDgyZSJ9signature : xfYii9lfWMHtMBgjK5U6NFN_6_Q9jw8UMQu8Jnvftbs
##### Header This is a `base64` encoded value. It contains details about the algorithm used to calculate the signature. This could have values such as HMAC SHA256 or RSA. For example—
// Sample JWT header{"alg" : "HS256","typ" : "JWT"}// is encoded toeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
This is also a base64
encoded value. This is the main part of the JWT, and contains details about the entity, and the request made (also called “claims”). For example—
// Sample JWT payload{"aud" : "8f47e41c-4684-4af1-b1b8-246c83112033", // This is the schemeID"iat" : 1572493356,"jti" : "c53447e3-993a-44ae-9c78-31f45c3c082e"}// is encoded toeyJhdWQiOiI4ZjQ3ZTQxYy00Njg0LTRhZjEtYjFiOC0yNDZjODMxMTIwMzMiLCJpYXQiOjE1NzI0OTMzNTYsImp0aSI6ImM1MzQ0N2UzLTk5M2EtNDRhZS05Yzc4LTMxZjQ1YzNjMDgyZSJ9
aud
is thescheme_id
in JWT. The entity sending the API response shares this with the entity making the API requests. Setu would provide this value under credentials for making calls to Setu APIs. You should set this value and share it with Setu to enable Setu to make calls to your APIs.iat
is the epoch timestamp in seconds, at which the request was issued. Requests older than 120 seconds (2 minutes) are considered stale, and hence must be rejected.jti
is a unique ID for every request that can be used to identify the request for logging, debugging and tracking purpose. Since this is unique for every request, the same JWT token should not be reused or repeated for different requests.
Signature
Using the algorithm specified in the header, along with the encoded header, encoded payload and secret, the signature is constructed in the following way—
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
The secret
is a private key shared between Setu and you, used to sign the token to verify the sender of the JWT, ie, if the claims are coming from the aud
that they claim to be coming from. The request is authenticated based on both the validity of the signature, and also the verification of each claim individually. For example—
Input string : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiI4ZjQ3ZTQxYy00Njg0LTRhZjEtYjFiOC0yNDZjODMxMTIwMzMiLCJpYXQiOjE1NzI0OTMzNTYsImp0aSI6ImM1MzQ0N2UzLTk5M2EtNDRhZS05Yzc4LTMxZjQ1YzNjMDgyZSJ9Secret : 7c2e036c-908f-48ba-abe3-cd8745a6fa99HA256 Signaure : xfYii9lfWMHtMBgjK5U6NFN_6_Q9jw8UMQu8Jnvftbs
You can try this on your command line by running—
echo -n "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiI4ZjQ3ZTQxYy00Njg0LTRhZjEtYjFiOC0yNDZjODMxMTIwMzMiLCJpYXQiOjE1NzI0OTMzNTYsImp0aSI6ImM1MzQ0N2UzLTk5M2EtNDRhZS05Yzc4LTMxZjQ1YzNjMDgyZSJ9.xfYii9lfWMHtMBgjK5U6NFN_6_Q9jw8UMQu8Jnvftbs" | openssl dgst -sha256 -hmac BBzKUWAZeAS2tBsk0FtJT4ep -binary|openssl base64 -e -A | sed s/+/-/ | sed -E s/=+$//
Read more about how we arrived at this command here ↗
#Sample code
Practically, you never need to worry about the encoding and decoding a JWT.
A lot of third party libraries exist that can do this for you easily —you can take a look at all the available libraries in most of the programming languages at Libraries for Token Signing/Verification section here ↗.
Setu / partner can accept a request as legitimate when the JWT format is recognised, ie, the payload is verified and the signature is valid. Requests can be rejected if—
- The time since generation of the token is more than 2 minutes.
- The
aud
claim is not verified. - The signature is invalid.
Below here are some samples in popular languages.
Python
import jwtimport datetimeimport uuidscheme_id = "8f47e41c-4684-4af1-b1b8-246c83112033"secret = "7c2e036c-908f-48ba-abe3-cd8745a6fa99"payload = {"aud" : scheme_id,"iat" : datetime.datetime.utcnow(),"jti" : str(uuid.uuid1())}// generate a token like thistoken = jwt.encode(payload, secret, algorithm="HS256")// And to decode the token and verify the aud claim—try:# verified claimjwt.decode(token, secret, audience=api_key)print("Verified token")except jwt.PyJWTError:# unverified claimprint("Token error")
PHP
<?phprequire __DIR__.'/vendor/autoload.php';use FirebaseJWTJWT;$key = "7c2e036c-908f-48ba-abe3-cd8745a6fa99";$aud = "8f47e41c-4684-4af1-b1b8-246c83112033";// create token$token = array("aud" => $aud,"iat" => 1572493356,"jti" => "c53447e3-993a-44ae-9c78-31f45c3c082e");$jwt = JWT::encode($token, $key);print_r($jwt);echo '<br/>';// Decode Logic/*** You can add a leeway to account for when there is a clock skew times between* the signing and verifying servers. It is recommended that this leeway should* not be bigger than a few minutes.** Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef*/JWT::$leeway = 60; // $leeway in seconds$decoded = JWT::decode($jwt, $key, array('HS256'));$audClaimInToken = $decoded -> aud;print_r($audClaimInToken);echo '<br/>';if($audClaimInToken == $aud){echo "Success";}else{echo "Fail";}?>
C#
using System;using System.IdentityModel.Tokens.Jwt;using System.Security.Claims;using System.Text;using Microsoft.IdentityModel.Tokens;public class JwtAuthManager{static string SecretKey = "ReallyLongSecret"; // this should come from your configuration filestatic string audience = "YourAudClaim";// this should come from your configuration filepublic string GenerateJWTToken(){byte[] symmetricKey = Encoding.ASCII.GetBytes(JwtAuthManager.SecretKey);var token_Handler = new JwtSecurityTokenHandler();var now = DateTime.UtcNow;var securitytokenDescriptor = new SecurityTokenDescriptor{Audience = JwtAuthManager.audience,IssuedAt = now,SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256)};var stoken = token_Handler.CreateToken(securitytokenDescriptor);var token = token_Handler.WriteToken(stoken);return token;}public bool ValidateToken(string token){var simplePrinciple = JwtAuthManager.GetPrincipal(token);Console.WriteLine(simplePrinciple);if (simplePrinciple == null)return false;// You can implement more validation to check whether username exists in your DB or not or something else.return true;}public static ClaimsPrincipal GetPrincipal(string token){try{var jwtTokenHandler = new JwtSecurityTokenHandler();var jwtToken = jwtTokenHandler.ReadToken(token) as JwtSecurityToken;if (jwtToken == null)return null;byte[] symmetricKey = Encoding.ASCII.GetBytes(JwtAuthManager.SecretKey);var validationParameters = new TokenValidationParameters(){ValidateAudience = true,RequireExpirationTime = false,ValidateIssuer = false,ValidAudience = JwtAuthManager.audience,IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)};SecurityToken securityToken;var principal = jwtTokenHandler.ValidateToken(token, validationParameters, out securityToken);Console.WriteLine("Principle");Console.WriteLine(principal);return principal;}catch (Exception e){Console.WriteLine(e);return null;}}}
Java
package com.setu.helpers;import com.auth0.jwt.JWT;import com.auth0.jwt.JWTVerifier;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.exceptions.JWTVerificationException;import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Date;import java.util.UUID;public class SetuJwtHelper {private String schemedId;private String secret;public SetuJwtHelper(String secret, String schemedId) {this.schemedId = schemedId;this.secret = secret;}public void setSchemedId(String schemedId) {this.schemedId = schemedId;}public void setSecret(String secret) {this.secret = secret;}public String yieldBearerToken() {Algorithm algorithm = Algorithm.HMAC256(this.secret);String token = JWT.create().withAudience(this.schemedId).withIssuedAt(new Date()).withClaim("jti", UUID.randomUUID().toString()).sign(algorithm);return "Bearer " + token;}public void verifyBearerToken (String bearerToken) throws JWTVerificationException {bearerToken = bearerToken.replace("Bearer ", "");Algorithm algorithm = Algorithm.HMAC256(this.secret);JWTVerifier verifier = JWT.require(algorithm).acceptLeeway(10).withAudience(this.schemedId).build(); //Reusable verifier instanceDecodedJWT jwt = verifier.verify(bearerToken);}}