Client ID Authentication with JWT (PKI)

Note: This article shows how to achieve strong authentication with Client IDs, using certificate based PKI factors, through JWT (JSON Web Tokens).
  • We recommend reading this page with having a background knowledge of PKI based solutions, certificates and public/private keys mechanisms.

  • To know more about tokens and JSON Web Tokens, refer to Tokens

Register a Client ID with JWT (PKI)

Refer to Managing Users, Groups and Roles - Client IDs with PKI (Certificate-based authentication)

Authenticating a Client ID with JWT (PKI)

What you need: 

  • A Client ID registered with a valid public certificate

  • The RSAprivate key associated with the Client ID, that was used to generate the public certificate.

Note: The only valid PKI based Authentication policy for Client IDs is AT_JWT. To know more about this policy and its constraints, see   Authentication Policies in the HID Authentication Service.

Typically, the authorisation service i.e. the Authentication Service is the system issuing tokens for you. With Client ID JWT authentication, things are the other way around! You are the one (your application) that issues and signs an ID Token with your Client ID and have the Authentication Service validate it. That is how the identification of your Client ID takes place, by validating a signature you issued with your private Key. Let's go through the steps to achieve this, it may sound more complex than it actually is. 

In the sequence diagram below, we can see the steps that will take place with JWT PKI based authentication:

  • You issue and sign an ID Token with your Client IDprivate Key

  • You send the signed ID Token along with a client_credentials OPENID request. 

  • The Authentication Service validates the signed ID Token against the public certificate that you previously registered for your Client ID. 

  • If the token is validated, the Authentication Service responds with an Access token, as for any other authentication factor. 

Issuing the ID Token 

The first step is to construct the ID Token that you will send to the Authentication Service. It consists of claims that indicate who issued the token (your application), who is the audience (the Authentication Service) and for how long the token is valid

The claims of the token's body are as follows:

  • sub (Subject): Your Client ID external ID. 

  • iss (Issuer): Your Client ID external ID. 

  • aud (Audience): The URL of your tenant on the Authentication Service.

  • nbf, iat (Not Before and Issued At): The moment the token was issued as a timestamp. Not Before corresponds to the moment the Authentication Service should start validating the token. This could be the current timestamp minus a few milliseconds. 

  • exp (Expiration): The expiration date. The example uses the current timestamp plus one hour. 

  • jti (JWT Identifier): A random to make the token unique. The example uses a number with minimum length of 9 digits. The recommendation is to use a 128 bits minimum alphanumeric random, such as UUID. 

{ 
   "sub": YOUR_CLIENTID_EXTERNALID, 
   "aud": "https://[base-server-url]/{tenant}/authn/token",
   "nbf": CURRENT_TIMESTAMP_IN_SECONDS - 30, 
   "iss": YOUR_CLIENTID_EXTERNALID,
   "exp": CURRENT_TIMESTAMP_IN_SECONDS + 3600,
   "jti": STRING( RANDOM ), 
   "iat": CURRENT_TIMESTAMP_IN_SECONDS - 30
}

Example with a Client ID whose external ID is 166923132596490579660806463389619325908380571836 on the Authentication Service

{ 
   "aud":"https://auth-us.api.hidglobal.com/idp/t(...)/authn/token",
   "exp":1569588561,
   "iat":1569584931,
   "iss":"166923132596490579660806463389619325908380571836",
   "jti":"358728253",
   "nbf":1569584931,
   "sub":"166923132596490579660806463389619325908380571836"}

Now that we have constructed a token, we need to sign it and construct the header, to form a complete ID Token. 

The header should contain the algorithm used to sign (the Authentication Service supports only RS256) and the Key ID (alias of the key, corresponding to the public key used when registering the Client ID): 

{
  "alg": "RS256",
  "kid": "YOUR_KEY_ID"}
Note:  
  • The signature with RS256 is a complex and sensible computation and should be processed by reputable cryptographic libraries.

  • See the examples below for technologies to use with Java, C# and JavaScript applications.

Construct the ID Token by adding the base64URL encoded header, body and signature concatenated by dots (.): 

base64URLEncode( headerJSON ) + "." + 
base64URLEncode( body ) + "." + 
base64URLEncode( 
	signWithRSA( 
		hashWithSHA256( 
			base64URLEncode( headerJSON ) + "." + base64URLEncode( body )  
		)
	, YOUR_PRIVATE_KEY ) 
)

Example: 

// base64URL encoded header
eyJhbGciOiJSUzI1NiIsImtpZCI6IlFJbllUTnBXUWxfLTNNV1hUYmFvOGMzRVhjQWlRSkI0czlLdXlvS3hTZW8ifQ 
.
// base64URL encoded body 
eyJzdWIiOiIzODMzNTk3MTIyNjI0NzcwMDMyNzE1OTAxNzM5MTUyNzA1Mjk3MTc1MzM5NjA5OSIsImF1ZCI6Imh0dHBzOi8vdGVzdC5hYWFzLmhpZGNsb3VkLmNvbS9pZHAvdDYzNmFjODJjMDI2OTU
3NzU4Nzg2MS9hdXRobi90b2tlbiIsIm5iZiI6MTU2OTU4NTUwMSwiaXNzIjoiMzgzMzU5NzEyMjYyNDc3MDAzMjcxNTkwMTczOTE1MjcwNTI5NzE3NTMzOTYwOTkiLCJleHAiOjE1Njk1ODkxMzEsIm
p0aSI6IjI4OTM2MzE3NCIsImlhdCI6MTU2OTU4NTUwMX0
.
// base64URL encoded RS256 signature of the body
HUARhDA2Cefcqpqbv4q2mSbG__0W31OLvxUODCK636PD-tr-o6MweX8m1_6V_wj27dOyuqm60rYZSGOmdsSfx9K
6PJh-81y43eCf7OgS5oHrlFghlTfFAqvxmf_3vE8tpQjCBfLXqQQWOwkAuebiXYUPZDfJtxLwIbUWRz6IWTrmFyziRkkoxA2gb
jdRebcC_m9nMrliZnM76fLb5aeNY7DaAD0H5fnFlecfOs9_OcrZ0i3-mmDJ4nt30d_B3iNSWkld3eBgpBkGkzsW8Y1sFNm9welmN3qn1mGy
H0mmdBz7HY4yRnAKDQ_p5y7WiDh3eayzOYZf4tR5j0IdReuLLw

Your ID Token is ready to be sent to the Authentication Service!

To know how to create and sign JSON Web Tokens with different technologies, see Building Applications.

Sending the ID Token 

Once the token is generated, we need to send it to the Authentication Service for validation. Simply use a client_credentials OPENID request, passing the ID Token you generated in the client_assertion attribute. The client_assertion_type indicates that you are passing a signed bearer token. 

POST https://[base-server-url]/{tenant}/authn/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
scope=openid&grant_type=client_credentials&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=eyJhbGciOiJ(...YOUR_ID_TOKEN)

The Response

The returned response contains an access token as well as an ID Token, it allows you to use the Authentication Service APIs and identifies your user's unique session: 

Client Credentials Authentication API Response

{
   "access_token":"lQVC9QAAAW1ylpXBSSvQNC9dzb9PG0ttxg1G+eMh",
   "id_token":"eyJr(...)",
   "token_type":"Bearer",
   "expires_in":86400
}

To see what we can do with the response, see Making the most of OPENID Tokens.