JWT and JOSE

By the specifications (RFC7519), “a JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties”. A JWT leverages Javascript Object Notation (JSON) to represent these claims, resulting in a small and simple token that is used by protocols such as OpenID Connect 1.0 to represent an identity to the application and OAuth 2.0 to represent an access token for API authorization. 

JWT defines the token format and uses complementary specifications to handle signing and encryption, this collection of specifications is known as JOSE (JavaScript Object Signing & Encryption) and consists of the following components:

JWS JSON Web Signature (RFC7515) - defines the process to digitally sign a JWT
JWE JSON Web Encryption (RFC7516) - defines the process to encrypt a JWT
JWA JSON Web Algorithm (RFC7518) - defines a list of algorithms for digitally signing or encrypting
JWK JSON Web Key (RFC7517) - defines how a cryptographic key and sets of keys are represented

This guide will describe the various components of the JOSE family of specifications and walk through common developer scenarios such as decoding and encoding a JWS.

 

JSON Web Token is a standard format that can be signed and/or encrypted. When a token is signed it uses JSON Web Signature (JWS), when encrypted it uses JSON Web Encryption (JWE). JWS and JWE are described below, however, for the purposes of this guide the examples will describe how to decode and encode a JWS.

The JWT specification defines seven optional, registered claims and allows for public and private claims to be included in the token, the seven registered claims are:

Claim Description
iss Issuer of the JWT
sub Subject that the JWT is representing
aud Audience for the JWT
exp Time the JWT is set to expire
nbf Time the JWT is valid from (not-before)
iat Timestamp when the JWT was issued (issued-at)
jti Unique identifier for the JWT (JWT ID)

Public claims can be registered by specific use cases. As an example, the OpenID Connect 1.0 specification defines claims such as auth_time, acr and nonce). Private claims can be used by an organisation to convey specific claims about the identity (for example first_name, last_name, department). As these claim names are not registered, care must be made to avoid name collisions.

 

A JSON Web Token that is signed with a digital signature uses the JSON Web Signature (JWS) specification. A JWS consists of three components, a header, a payload and the digital signature. These three sections are Base64url encoded and separated by periods.

BASE64URLENCODE(<header>) + "." + BASE64URLENCODE(<payload>) + "." + BASE64URLENCODE(<signature>)

Note: JWT assumes base64url encoding/decoding. This is slightly different than regular base64 encoding. Refer to RFC4648 for specifics regarding Base64 vs Base64 URL safe encoding.

An example JWS (an OpenID Connect id_token) is included below. For details on how to decode this token, refer to the "How it Works" section.

eyJhbGciOiJSUzI1NiIsImtpZCI6Imkwd25uIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJpbV9vaWNfY2xpZW50IiwianRpIjoidWY5MFNLNH
dzY0ZoY3RVVDZEdHZiMiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTM5NDA2MDg1MywiZXhwIjoxMzk0MDYx
MTUzLCJub25jZSI6ImU5NTdmZmJhLTlhNzgtNGVhOS04ZWNhLWFlOGM0ZWY5Yzg1NiIsImF0X2hhc2giOiJ3Zmd2bUU5VnhqQXVkc2w5bG
M2VHFBIn0.lr4L-oT7DJi7Re0eSZDstAdOKHwSvjZfR-OpdWSOmsrw0QVeI7oaIcehyKUFpPFDXDR0-RsEzqno0yek-_U-Ui5EM-yv0Pia
UOmJK1U-ws_C-fCplUFSE7SK-TrCwaOow4_7FN5L4i4NAa_WqgOjZPloT8o3kKyTkBL7GdITL8rEe4BDK8L6mLqHJrFX4SsEduPk0CyHJS
ykRqzYS2MEJlncocBBI4up5Y5g2BNEb0aV4VZwYjmrv9oOUC_yC1Fb4Js5Ry1t6P4Q8q_2ka5OcArlo188XH7lMgPA2GnwSFGHBhccjpxh
N7S46ubGPXRBNsnrPx6RuoR2cI46d9ARQ

 

JSON Web Encryption (JWE)

Using the JSON Web Encryption (JWE) specification a JWT can be encrypted. This can be used for additional confidentiality and security of the token contents (in addition to the security of the token during transport using transport layer security).

 

JSON Web Algorithms (JWA)

The JWA specification defines a standard list of algorithms the parties can use for signing or encryption. An example of JWA signing algorithms are:

"alg" Value Signature Method Signing Key
NONE No Digital Signature N/A
HS256 HMAC w/ SHA-256 hash Uses the client secret of the OAuth2 client
HS384 HMAC w/ SHA-384 hash Uses the client secret of the OAuth2 client
HS512 HMAC w/ SHA-512 hash Uses the client secret of the OAuth2 client
RS256 RSA PKCS v1.5 w/ SHA-256 hash Public key available from the JWKS (see below)
RS384 RSA PKCS v1.5 w/ SHA-384 hash Public key available from the JWKS (see below)
RS512 RSA PKCS v1.5 w/ SHA-512 hash Public key available from the JWKS (see below)
ES256 ECDSA w/ P-256 curve and SHA-256 hash Public key available from the JWKS (see below)
ES384 ECDSA w/ P-384 curve and SHA-384 hash Public key available from the JWKS (see below)
ES512 ECDSA w/ P-521 curve and SHA-512 hash Public key available from the JWKS (see below)

Note: There are security implications with accepting tokens signed with the "none" algorithm. A developer should only accept a JWT using the "none" algorithm if the token is secured by other means and the "none" algorithm is expected.

 

JSON Web Key (JWK)

The JSON Web Key specification defines how the asymmetric keys are represented in the JSON format and introduces a key set collection (JWKS) which provides a way for a provider to publish their signing and encryption keys.

Given the following JSON Web Key Set (JWKS) below (an OpenID Connect 1.0 JWKS), two keys are defined (an ECDSA key and an RSA key):

{
  "keys":[
    {
      "kty":"EC",
      "kid":"i0wng",
      "use":"sig",
      "x":"AXYMGFO6K_R2E3RH42_5YTeGYgYTagLM-v3iaiNlPKFFvTh17CKQL_OKH5pEkj5U8mbel-0R1YrNuraRXtBztcVO",
      "y":"AaYuq27czYSrbFQUMo3jVK2hrW8KZ75KyE8dyYS-HOB9vUC4nMvoPGbu2hE_yBTLZLpuUvTOSSv150FLaBPhPLA2",
      "crv":"P-521"
    },
    ...
    {
      "kty":"RSA",
      "kid":"i0wnn",
      "use":"sig",
      "n":"mdrLAp5GR8o5d5qbwWTYqNGuSXHTIE6w9HxV445oMACOWRuwlOGVZeKJQXHM9cs5Dm7iUfNVk4pJBttUxzcnhVCRf
9tr20LJB7xAAqnFtzD7jBHARWbgJYR0p0JYVOA5jVzT9Sc-j4Gs5m8b-am2hKF93kA4fM8oeg18V_xeZf11WWcxnW5YZwX
9kjGBwbK-1tkapIar8K1WrsAsDDZLS_y7Qp0S83fAPgubFGYdST71s-B4bvsjCgl30a2W-je9J6jg2bYxZeJf982dzHFqV
QF7KdF4n5UGFAvNMRZ3xVoV4JzHDg4xe_KJE-gOn-_wlao6R8xWcedZjTmDhqqvUw",
      "e":"AQAB"
    },
  ...]
}

Note: for the RSA key, the "n" component is the modulus and the "e" component is the exponent. Both are base64urlencoded values.

 

The most common scenario faced by developers is to be able to decode a JWS that is provided to the application (for example in an OpenID Connect authentication), this scenario is described below.

Note: It is strongly recommended to make use of common libraries for JWT and JWS processing to avoid introducing implementation specific bugs.

For this example we will use the id_token described in the above section. This is a signed JWT (JWS):

eyJhbGciOiJSUzI1NiIsImtpZCI6Imkwd25uIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJpbV9vaWNfY2xpZW50IiwianRpIjoidWY5MFNLNH
dzY0ZoY3RVVDZEdHZiMiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTM5NDA2MDg1MywiZXhwIjoxMzk0MDYx
MTUzLCJub25jZSI6ImU5NTdmZmJhLTlhNzgtNGVhOS04ZWNhLWFlOGM0ZWY5Yzg1NiIsImF0X2hhc2giOiJ3Zmd2bUU5VnhqQXVkc2w5bG
M2VHFBIn0.lr4L-oT7DJi7Re0eSZDstAdOKHwSvjZfR-OpdWSOmsrw0QVeI7oaIcehyKUFpPFDXDR0-RsEzqno0yek-_U-Ui5EM-yv0Pia
UOmJK1U-ws_C-fCplUFSE7SK-TrCwaOow4_7FN5L4i4NAa_WqgOjZPloT8o3kKyTkBL7GdITL8rEe4BDK8L6mLqHJrFX4SsEduPk0CyHJS
ykRqzYS2MEJlncocBBI4up5Y5g2BNEb0aV4VZwYjmrv9oOUC_yC1Fb4Js5Ry1t6P4Q8q_2ka5OcArlo188XH7lMgPA2GnwSFGHBhccjpxh
N7S46ubGPXRBNsnrPx6RuoR2cI46d9ARQ

 

Step 1 : Split the JWS into its components

First we split the token by periods to end up with three separate components: the JOSE header, the JWT payload and the digital signature. These three components are base64urlencoded and therefore will need to be base64urldecoded to reveal the contents.

The JOSE header in this example contains the algorithm used to sign ("alg":"RS256") and a reference to the key that can be used to verify it ("kid":"i0wnn"):

Component Value Value Decoded
JWT Header
eyJhbGciOiJSUzI1NiIsImtpZCI6Imkwd25uIn0

 
{
"alg":"RS256",
"kid":"i0wnn"
}

 

The second component is the JWT payload which contains the JWT claims. The payload of the above example is decoded as follows:

Component Value Value Decoded
JWT Payload eyJzdWIiOiJqb2UiLCJhdWQiOiJpbV9vaWN
fY2xpZW50IiwianRpIjoidWY5MFNLNHdzY0
ZoY3RVVDZEdHZiMiIsImlzcyI6Imh0dHBzO
lwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6
MTM5NDA2MDg1MywiZXhwIjoxMzk0MDYxMTU
zLCJub25jZSI6ImU5NTdmZmJhLTlhNzgtNG
VhOS04ZWNhLWFlOGM0ZWY5Yzg1NiIsImF0X
2hhc2giOiJ3Zmd2bUU5VnhqQXVkc2w5bGM2
VHFBIn0 
 
{
"sub":"joe",
"aud":"im_oic_client",
"jti":"uf90SK4wscFhctUT6Dtvb2",
"iss":"https:\/\/localhost:9031",
"iat":1394060853,
"exp":1394061153,
"nonce":"e957ffba-9a78-4ea9-8eca-ae8c4ef9c856",
"at_hash":"wfgvmE9VxjAudsl9lc6TqA"

 

Finally, the third component is the digital signature of section 1 and 2 (period concatenated). The algorithm and key reference used to create and verify the signature is defined in the JWT Header.

Component Value Value Decoded
JWT Signature lr4L-oT7DJi7Re0eSZDstAdOKHwSvjZfR-OpdWSOmsrw0QVeI7oaIce
hyKUFpPFDXDR0-RsEzqno0yek-_U-Ui5EM-yv0PiaUOmJK1U-ws_C-f
CplUFSE7SK-TrCwaOow4_7FN5L4i-4NAa_WqgOjZPloT8o3kKyTkBL7
GdITL8rEe4BDK8L6mLqHJrFX4SsEduPk0CyHJSykRqzYS2MEJlncocB
BI4up5Y5g2BNEb0aV4VZwYjmrv9oOUC_yC1Fb4Js5Ry1t6P4Q8q_2ka
5OcArlo188XH7lMgPA2GnwSFGHBhccjpxhN7S46ubGPXRBNsnrPx6Ru
oR2cI46d9ARQ 
N/A

 

Step 2 : Validating the Digital Signature

To validate the signature, take the JWT header and the JWT payload and join with a period. Validate that value against the third component of the JWT using the algorithm defined in the JWT header. Using the above ID token as an example:

Signed data (JWT Header + “.” + JWT Payload):

eyJhbGciOiJSUzI1NiIsImtpZCI6Imkwd25uIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJpbV9vaWNfY2xpZW50IiwianRpIjoidWY5MFNLNHdz
Y0ZoY3RVVDZEdHZiMiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTM5NDA2MDg1MywiZXhwIjoxMzk0MDYxMTUz
LCJub25jZSI6ImU5NTdmZmJhLTlhNzgtNGVhOS04ZWNhLWFlOGM0ZWY5Yzg1NiIsImF0X2hhc2giOiJ3Zmd2bUU5VnhqQXVkc2w5bGM2VHFB
In0

Signature value to verify:

lr4L-oT7DJi7Re0eSZDstAdOKHwSvjZfR-OpdWSOmsrw0QVeI7oaIcehyKUFpPFDXDR0-RsEzqno0yek-_U-Ui5EM-yv0PiaUOmJK1U-ws_C
-fCplUFSE7SK-TrCwaOow4_7FN5L4i-4NAa_WqgOjZPloT8o3kKyTkBL7GdITL8rEe4BDK8L6mLqHJrFX4SsEduPk0CyHJSykRqzYS2MEJln
cocBBI4up5Y5g2BNEb0aV4VZwYjmrv9oOUC_yC1Fb4Js5Ry1t6P4Q8q_2ka5OcArlo188XH7lMgPA2GnwSFGHBhccjpxhN7S46ubGPXRBNsn
rPx6RuoR2cI46d9ARQ

Note: The validation of the signature will depend on the language and/or platform in use. It is recommended to use a published library to perform the signature verification.

 

Another common developer function is to encode a JWS to provide to a service (such as to call the PingID API). The steps involved are basically the reverse of the decoding process, and the following example creates a JWS that is passed to the PingID "GetUserDetails" API call.

Note: It is strongly recommended to make use of common libraries for JWT and JWS processing to avoid introducing implementation specific bugs.

 

Step 1 : Generate the JOSE Header

First we create the JSON object for the JWT header. The format of the header is defined by the API.

JSON object:

{
  "alg": "HS256",
  "org_alias": "aaaa-1234-xyzzy-12345678",
  "token": "aa01330de012031"
}

 

Base64urlencoded value:

ewogICJhbGciOiAiSFMyNTYiLAogICJvcmdfYWxpYXMiOiAiYWFhYS0xMjM0LXh5enp5LTEyMzQ1Njc4IiwKICAidG9rZW4iOiAi
YWEwMTMzMGRlMDEyMDMxIgp9

 

Step 2 : Generate the JWT Payload

The payload follows the same process, create the JSON object and base64urlencode the value.

JSON payload:

{
  "reqHeader": {
    "orgAlias": "aaaa-1234-xyzzy-12345678",
    "secretKey": "aa01330de012031",
    "timestamp": "2015-07-23 11:19:56.38",
    "version": "4.5",
    "locale": "en",
  },
  "reqBody": {
    "getSameDeviceUsers": true,
    "userName": "marcher",
    "adminId": null,
    "clientData": null
  }
}

Base64urlencoded value:

ewogICJyZXFIZWFkZXIiOiB7CiAgICAib3JnQWxpYXMiOiAiYWFhYS0xMjM0LXh5enp5LTEyMzQ1Njc4IiwKICAgICJzZWNyZXRL
ZXkiOiAiYWEwMTMzMGRlMDEyMDMxIiwKICAgICJ0aW1lc3RhbXAiOiAiMjAxNS0wNy0yMyAxMToxOTo1Ni4zOCIsCiAgICAidmVy
c2lvbiI6ICI0LjUiLAogICAgImxvY2FsZSI6ICJlbiIsCiAgfSwKICAicmVxQm9keSI6IHsKICAgICJnZXRTYW1lRGV2aWNlVXNl
cnMiOiB0cnVlLAogICAgInVzZXJOYW1lIjogIm1hcmNoZXIiLAogICAgImFkbWluSWQiOiBudWxsLAogICAgImNsaWVudERhdGEi
OiBudWxsCiAgfQp9

 

Step 3 : Generate the Digital Signature

To generate the digital signature, we join the base64urlencoded values for the header and the payload (using a period to separate) and generate the digital signature according to the algorithm specified. In this case we are using the HS256 alg value (which uses the HMAC algorithm with a SHA-256 hash).

BASE64URLENCODE( HMACSHA256( BASE64URLENCODE(<header>) + "." + BASE64URLENCODE(<payload>), <symmetric key> ) )

This will result in a value similar to:

Vi3JphmmikK50FMPgAkPfaSoLsoHNhwMuUDGsrQxUH4=

 

Step 4 : Put it all Together

The resulting JWS is a concatenation of the encoded header, payload and signature:

ewogICJhbGciOiAiSFMyNTYiLAogICJvcmdfYWxpYXMiOiAiYWFhYS0xMjM0LXh5enp5LTEyMzQ1Njc4IiwKICAidG9rZW4iOiAi
YWEwMTMzMGRlMDEyMDMxIgp9.ewogICJyZXFIZWFkZXIiOiB7CiAgICAib3JnQWxpYXMiOiAiYWFhYS0xMjM0LXh5enp5LTEyMzQ
1Njc4IiwKICAgICJzZWNyZXRLZXkiOiAiYWEwMTMzMGRlMDEyMDMxIiwKICAgICJ0aW1lc3RhbXAiOiAiMjAxNS0wNy0yMyAxMTo
xOTo1Ni4zOCIsCiAgICAidmVyc2lvbiI6ICI0LjUiLAogICAgImxvY2FsZSI6ICJlbiIsCiAgfSwKICAicmVxQm9keSI6IHsKICA
gICJnZXRTYW1lRGV2aWNlVXNlcnMiOiB0cnVlLAogICAgInVzZXJOYW1lIjogIm1hcmNoZXIiLAogICAgImFkbWluSWQiOiBudWx
sLAogICAgImNsaWVudERhdGEiOiBudWxsCiAgfQp9.Vi3JphmmikK50FMPgAkPfaSoLsoHNhwMuUDGsrQxUH4=

 

It is highly recommended to use a common library to work with JWT's. Below are some common libraries for various languages.

Note: Below are example libraries for various languages, these are not endorsed or supported by Ping Identity however you can use the Ping Identity developer community for any questions and issues.

Java JOSE4J - by Brian Campbell
JavaScript jsrsasign - by Kenji Urushima
.NET Microsoft System.IdentityModel.Tokens.JwtSecurityToken (Microsoft .NET 4.5+)