SAML2 vs JWT: Understanding JSON Web Token (JWT)

In this post, we begin our exploration of the JSON Web Token (JWT) specification as part of the SAML v2.0 vs JWT Series. To understand JWT use cases, we must also look at OpenID Connect v1.0, OAuth v2.0, and and a few related specifications — the JWT spec by itself is not very interesting or useful. We saw this with SAML 2.0 part of this series — WS-Trust, WS-Security, XML DSig, and numerous other foundational specs are referenced. The JWT specification is much smaller than the SAML2 specs. It is much more basic, defining only the structure of the token, but not any of the protocols that utilize the token and make it useful in the real world. The OAuth2 and OpenID Connect specs do that — that’s coming soon. As usual, I am coming at this from the perspective of the enterprise IT shop; I’m thinking big, process oriented, auditing, lots of rules, lots of security requirements, etc, etc. It may not all be relevant to your situation, but, much of it probably is. This post isn’t about OAuth2 and OpenID Connect — we’ll get to those soon.

A Brief History of JWT

The earliest drafts of the JWT specification were called OpenID Artifact Binding proposal. The first draft of the JWT specification was published in July, 2011 — notice this earliest published spec didn’t reference JSON Web Signature (JWS) or JSON Web Encryption (JWE). Early versions of this spec were influenced by the desire to have a JSON-based identity token that supported signatures and encryption, but also supported multiple crypto algorithms while remaining in the spirit of Salmon Protocol’s Magic Signature’s simplicity. In July, 2011, the IETF WOES (Web Object Encryption Signing) Working Group (an unfortunate name) started to extend work from the CMS (Cryptographic Message Syntax) spec (RFC 3852). Later that year, the JWT and WOES Working Groups merged to form the JSON Object Signing Encryption (JOSE) Working Group. This working group led to the JWS and JWE specifications (among others) — these are foundational building blocks for the JWT spec. The JWT spec (RFC 7519), JWS Spec (RFC 7515), and JWE Spec (RFC 7516) were published in May, 2015.

What is JWT?

The JSON Web Token Spec (RFC 7519) defines a type of bearer token that can contain key:value pairs of attributes that are used to satisfy Role-Based Access Control (RBAC) or Attribute-Based Access Control (ABAC) security models. From the specification,

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

JWT tokens are a JSON (RFC 4627) data structure with name:value pairs representing claims that make up the Payload of a JWS structure (or JWE plaintext as described above). The JWS signature capabilities mean the information in the token and its source can be validated by third-parties without going back to the Identity Provider that generated the token. The JWE encryption capabilities mean that only the desired parties will be able to read the contents of the token.

A JWT contains JSON-based text that contains (or can contain) information about:

  • The authenticated principal (subject)
  • Custom security attributes describing the user (private claims)
  • Information about the IdP (issuer)
  • Information about the SP (audience information)
  • Valid time frame (starting date, expiration date, issuing date)
  • Digital signature
  • How the principal was authenticated
  • Encryption information

I wrote a nearly identical list in the original description of SAML2 in this series, which suggests these two technologies are very similar.

Just as we saw in the JWS blog post, the JWT token structure includes:

  • Header
  • Payload
  • Signature

A typical example of a JWT Payload (generated by AAD) would be:

{
“aud”: “https://app1.levvel.io/login",
“iss”: “https://sts.windows.net/75de389c-8f67-4084-9065-3a9c31e1db13/",
“iat”: 1483691542,
“nbf”: 1483691542,
“exp”: 1483695442,
“acr”: “1”,
“amr”: [
“pwd”
],
“appid”: “80363411-f180–4a51–80ba-9b63770b9ac4”,
“appidacr”: “0”,
“family_name”: “User1”,
“given_name”: “Test”,
“ipaddr”: “1.1.1.1”,
“name”: “Test User1”,
“oid”: “42cd4e91–1c39–45ec-a0cb-13361157487b”,
“platf”: “14”,
“scp”: “user_impersonation”,
“sub”: “Hd6Ymh1ICumL3_MxJcdM1LaVFlbkSCNmmo6wlG7OFDg”,
“tid”: “75de389c-8f67–4084–9065–3a9c31e1db13”,
“unique_name”: “test1@rcbj.net”,
“upn”: “test1@rcbj.net”,
“ver”: “1.0”
“roles”: [
“User”
]
}

The JWT Header would look something like:

{
typ: “JWT”,
alg: “RS256”,
x5t: “kriMPdmBvx68skT8-mPAB3BseeA”
}

I used an AAD JWT token for this example because it’s a platform I am very familiar with and it has many useful features in the enterprise identity space.

These components are base-64 encoded and separated by ‘.’. So, we have:

BASE64URL(UTF8(JWS Protected Header)) || ‘.’ || BASE64URL(JWS Payload) || ‘.’ || BASE64URL(JWS Signature)

as the general structure of the JWT Token. This will look like:

eyJhbGciOiJSUzI1NiJ9.eyJhIjoiYiIsImMiOiJkIiwiZSI6MX0.CNMaYaDGU3ZhFV1ve6p3sAdYXhEklej8DVIAMqIWCkpNmT6Jp7iigcndXwH5q3WQFHiswgIQU5-_-4rV3jKGptCROmEyWPW8_elhYH1apzAyjOjyZ55ygv37xKHzIFhixzAwmXlAv4pfD4lVelYWVNOSN7REA0QJeCy2vKdqZ5cjqCXQ1lkQUlzOE7dpuNoAkhAhAJJ8HaamFKy7Gl7uwmqbIr-dVYv21d_9O7mO26n0gy3zWXD2nJDxU5Mzl2pZd8-sFvUr9Kmp_YkeRMh4bSe0fr1Uc_YgkjpmYUyu7kaxRWTbAdJ3GwqWFMUDiyfhHdzvZPZyU4VkWreimoydMA

The details of how the signature are generated can be found here. We’re also assuming that there is no encryption (JWE) used.

The JWT spec defines the following optional claims (detailed descriptions are in the spec):

“iss” (Issuer) Claim: the principal that issued the JWT. This claim is optional.

“sub” (Subject) Claim: the principal that is described by the JWT. The claims in a JWT are normally statements about the subject.

“aud” (Audience) Claim: the recipient(s) that the JWT is intended for. This value is highly application (context) specific.

“exp” (Expiration Time) Claim: the expiration time of the JWT. It is a number containing a NumericDate value..

“nbf” (Not Before) Claim: the time before which the JWT is not valid. The claim is optional.

“iat” (Issued At) Claim: the time at which the JWT was issued — determines age of the token. Typically, the same as the “nbf” claim.

“jti” (JWT ID) Claim: a unique identifier for the JWT from the generating Identity Provider.

In addition, the JWS spec defines these parameters that may be used in JWT tokens. Between the JWS spec’s claims and the list above, there aren’t many parameters that are actually required — another aspect of the drive for simplicity. Though, from a practical standpoint, any non-trivial use-case is going to have many of these parameters and likely a variety of private claims as well.

Additional private claims can be used as long as the producer and consumer of the JWT token agree on their use. We can see an example of a private claim in the AAD JWT token above with the “upn” claim — User Principal Name (UPN) is an Active Directory attribute that uniquely describes a user that carried over into the Azure Active Directory world. Additional information about Azure Active Directory tokens and claims can be found here.

For the AAD-generated JWT given above:

audience (aud): https://app1.levvel.io/login; this audience represents a web application that has this URL.

issuer (iss): contains https://sts.windows.net/75de389c-8f67-4084-9065-3a9c31e1db13/; this uniquely identifies the AAD tenant that generated this token.

“issued at” (iat), “not before” (nbf), and expires (exp): contains the times (down to the second) of when the JWT was issued, the time before which it is not valid (typically the same as the Issued at time), and the expires time. These values are in coordinated universal time; the numbers can be converted to human readable form.

version (ver): claim has the version number of this token — the JWT specification version.

tenant id (tid): parameter is the unique identifier for the AAD tenant that generated this token.

authentication method (amr): contains information about how the Principal was authenticated (in this case with userid + password).

roles: array of strings that contain human readable names of application roles the Principal described by this token is a member of; this information can be used for Role-Based Access Control decisions — Azure Active Directory application roles is for another post.

object Id (oid): parameter contains the object identifier; this uniquely describes an object in AAD.

user principal name (upn): the username of the Principal; this is Microsoft private claim that is of the form user@domain (it looks a lot like an email address, but it is not).

unique_name: a human readable name value for the user that may not be unique within the tenant; however, the name would imply otherwise — not entirely sure what they were thinking there.

subject parameter (sub): immutable, unique identifier for the AAD object that represents the Principal the token describes.

family_name: the family (or surname) name of the Principal.

given_name: the given or first name of the Principal.

appid: the client identifier that was used in the OAuth (OpenID Connect) call that was generated the token.

scope (scp): impersonation permissions granted to the application described by the appid parameter — we will come back to the topic of impersonation in a future series of posts.

application authentication context class reference (appidacr): information about how the client application was authenticated. if the client is public, the value will always be ‘0’; if the client is confidential, the value will be ‘1’.

authentication context class reference (acr): information about how the Principal was authenticated; a value of “0” indicates authentication did not meet the requirements of ISO/IEC 29115.

The parameters in the Header are all defined in the JWS spec and covered in our earlier discussion.

JWTs can be used for authentication to server-side components (such as APIs or other resources) and identity propagation between API Actors. I’ve touched on the concept of secure identity propagation before. This is a mechanism that securely transmits the identity of the original caller or request initiator to downstream systems beyond where the original caller (especially a human actor) is directly participating such as between an API Gateway and API Provider.

In the next part of this series, we will look at JWT use cases.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.