SAML2 vs JWT: Understanding OpenID Connect Part 1

This post builds upon what we learned about OAuth2 and JWT in previous posts. OpenID Connect will give us the final building block for the JWT-related use cases that this series will explore. The goal of this blog post is to provide a deep understanding of the OpenID Connect spec without having to go read the specs.

History of OpenID

There are three generations of OpenID technology; OpenID Connect (OIDC) is the third — it was published in November, 2014. The original (first generation) OpenID specification was published in May, 2005 by Brad Fitzpatrick; it was originally referred to, in the tradition of YACC — Yet Another Compiler Compiler — and YAML — Yet Another Markup Language, as YADIS (Yet Another Distributed Identity System). The second-generation OpenID specification (OpenID v2.0) was released in December, 2007.

The first generation spec was not widely adopted. The second generation spec had much greater adoption and maturity; however, there were design limitations including Relying Parties (RPs) could be web applications (but not native applications) and it used XML.

What is OpenID Connect?

From openid.net, “OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner.” This “REST-like manner” makes OIDC more like an API (in line with OAuth2) than the previous generations of OpenID. OIDC extends the OAuth2 Authorization Code Grant (three-legged OAuth).

OIDC builds on the lessons of the early OpenID protocols and SAML2 including:

  • keep things simple
  • build on top of OAuth2
  • use easy-to-consume tokens (JWT)

OIDC can integrate with

  • traditional web applications
  • mobile apps
  • Single Page Applications (SPA apps)
  • server applications
  • most other actors

OIDC consists of the following specifications (from openid.net):

  • Core (required): core OIDC functionality: authentication built on top of OAuth 2.0 and the use of Claims to communicate end-user information.
  • Discovery (optional): How Clients dynamically discover information about OpenID Providers (ie, Authorization Servers or Identity Providers).
  • Dynamic Registration (optional): how clients dynamically register with OpenID Providers.
  • OAuth 2.0 Multiple Response Types(required): Defines several specific new OAuth 2.0 response types.
  • OAuth 2.0 Form Post Response Mode (Optional): Defines how to return OAuth 2.0 Authorization Response parameters (including OIDC Authentication Response parameters) using HTML form values that are auto-submitted by the User Agent using HTTP POST
  • Session Management (Optional): Defines how to manage OIDC sessions, including post Message-based logout functionality
  • Front-Channel Logout (Optional): Defines a front-channel logout mechanism that does not use an OP iframe on RP pages
  • Back-Channel Logout (Optional): Defines a logout mechanism that uses back-channel communication between the OP and RPs being logged out.

Two implementer’s guides are also available to serve as self-contained references for implementers of basic Web-based Relying Parties:

Also, a protocol migration specification is available:

The same site, also has the basis of the following chart that shows these specifications and the supporting specs:

The OIDC family of specs and supporting specs.

Actors

The OIDC spec defines several actors.

Relying Party (RP): This is a new term borrowed from the SAML2 spec. OAuth2 Client application requiring End-User Authentication and Claims from an OpenID Provider. The OpenID Provider (see below) securely returns the Authentication Response to the OAuth2 Client so that it can be relied upon; thus, the client is called a Relying Party in this context.

OpenID Provider (OP): OAuth 2.0 Authorization Server that is capable of Authenticating the End-User and providing Claims to a Relying Party about the Authentication event and the End-User (per the OIDC spec).

End-User: This term was present in OAuth2. The principal that is authenticated (a human most likely).

User Agent: This term was present in OAuth2. Something that initiates HTTPS requests and can handle the redirects generated by the Client and OpenID Provider. This is usually going to be a User Agent or a library that can process the OIDC calls.

Resource Server: This term was present in OAuth2. The server hosting the protected resources, capable of accepting and responding to protected resource requests using Access Tokens.

Changes To The ID Token (JWT)

The claims defined in the JWT spec are described in the “SAML2 vs. JWT: Understanding JSON Web Token (JWT)” post. Beyond what is required by the JWT and JWS specs, the OIDC spec requires the following Claims in the JWT acting as an ID Token. Note, that these requirements do not necessarily have to apply to a JWT token acting as the Access Token. Note, the descriptions give here are paraphrased from the full description of the spec.

“iss” (Issuer) Claim: identifier for the Identity Provider that issued the JWT. This is a case sensitive URL using the HTTPS scheme. This claim is now required.

“sub” (Subject) Claim: subject identifier. A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client. This claim is now required.

“aud” (Audience) Claim: audience(s) that this ID Token is intended for. It must contain the OAuth 2.0 client_id of the Relying Party as an audience value. It can be an array of case sensitive strings; it may also contain a single case-sensitive string (the aforementioned client_id). This claim is now required.

“exp” (Expiration) Claim: time on or after which the ID Token must not be accepted for processing. A clock skew can be added by the Client. This claim is now required.

“iat” (Issued At time) Claim: time at which the JWT was issued. This claim is now required.

“auth_time” (time subject was authenticated) Claim: time when the End-User authentication occurred. May be required depending upon use case.

“nonce” (nonce) Claim: string value used to associate a Client session with an ID Token, and to mitigate replay attacks. May be required depending on the use case.

“acr” (Authentication Context Class Reference) Claim: a string containing information about how the Principal was authenticated; a value of “0” indicates authentication did not meet the requirements of ISO/IEC 29115. This claim is optional.

“amr” (Authentication Methods References) Claim: a JSON array of strings that are authentication method identifiers, which describe how the end user authentication was done. The identifiers are beyond the scope of the OIDC spec; so, each OP will define their own. This claim is optional.

“azp” (Authorized Party) Claim: the party to which the ID Token was issued. If present, it MUST contain the OAuth 2.0 Client ID of this party. This is only needed when the audience array contains a single value (the Client Identifier of the RP) and that value is different from the Authorized Party. This claim is optional.

“at_hash” (Access Token hash value) Claim: hash value of the access_token using the hash algorithm listed in the alg Header Parameter. This claim may be required depending upon the flow.

“c_hash” (authorization code hash) Claim: hash value of the code parameter value using the hash algorithm listed in the “alg” Header Parameter. Basically, this is the same thing as the “at_hash” Claim, but on the authorization code. This claim may be required depending upon the flow.

“sub_jwk” (Self-Issued OpenID Provider digital signature public key) Claim: Public key used to check the signature of an ID Token issued by a Self-Issued (personal, self-hosted OP that issues self-signed tokens) OP. The key is a bare key in JWK format (not an X.509 format). If the OP is not Self-Issued, this claim should not be used.

See Section 2 of the OIDC spec for more information about these claims. Claims that are not understood must be ignored. The JWT token acting as an ID Token should not contain the JWS or JWE Header Parameter fields:

  • x5u
  • x5c
  • jku
  • jwk

Instead, this information should be provided through the OP’s Discovery Document or other means (see Section 10 of the OIDC spec).

The OIDC spec also defines numerous standard claims that can be included (by request) in the ID Token or UserInfo response. Whether additional claims are included in the ID Token or retrieved from the UserInfo Endpoint will depend upon the OP vendor’s supported functionality.

The JWT acting as an ID Token must be signed (JWS) and optionally encrypted (JWE). If encryption is used, then the JWT must be signed and then encrypted. These steps provide “authentication, integrity, non-repudiation, and optionally, confidentiality.”

How is OIDC Different From OAuth 2.0?

Directly from the spec, we have “the primary extension that OIDC makes to OAuth 2.0 to enable End-Users to be Authenticated is the ID Token data structure.So, OIDC brings JWT into the picture — and, by extension turns it into something that starts to become very comparable to what’s in play in the SAML2 use cases.

The OAuth2 and OIDC specs address many of the same use cases, but, the OAuth2 spec comes at this from the perspective of delegated authorization (of a client application based upon access the end user allows to his/her resources hosted on a third-party server). The OAuth2 spec addresses a variety of use cases beyond three-legged OAuth, but this is where the original OAuth spec began and what OIDC extends. In each case, there is a focus on delegated authorization. The details of how authentication works and more advanced authentication-centric features are not defined in OAuth2 — this is where OIDC begins. OIDC is an authentication protocol; it is built on top of the OAuth2 Authorization Code Grant. Stated another way, OIDC is an OAuth2 profile.

OIDC adds the following authentication features to OAuth2:

  • Claims standardization (the standard set of claims present in the ID Token).
  • Mandates use of a digitally signed (and optionally encrypted) token (JWT) as the ID Token data structure that contains user information (Claims). Interestingly, this can also be accomplished by mandating that the Access Token itself be a JWT; though, this has security implications that we will look at later. The introduction of the ID Token means that the authentication step on the RP now relies upon the ID Token and not the Access Token (unlike OAuth2).
  • Provides a SAML2 SAML Artifact-like mechanism via the UserInfo Endpoint. This allows for scoping what claims are seen by various actors.
  • Allows forcing authentication of an end user (even if they are already logged into the OP)
  • Ability for Clients to request additional claims (ID Token or Userinfo Endpoint as discussed earlier)
  • Identify how the end-user was authenticated (ie, the Authentication Context, acr claim, in the ID Token).
  • Protection level on Access Tokens. This refers to the use of encryption (JWE) and signatures (JWS) on the ID Token. These details would be configured during the client registration process. While researching this article, I was looking around for a run-time mechanism (parameter attached to an authentication request) that would allow the Client to specify what protection level should be included with the ID Token, but it does not appear that there is one.
  • Distributed and aggregate claims support. This series of post doesn’t go into further detail on these features. See Section 5.6.2 of the OIDC spec.
  • Dynamic introductions (client introductions and on-boarding). Likewise, we don’t go into further detail on this topic either.

OIDC Endpoints

An OP advertises the following endpoints.

Authorization Endpoint

The Authorization Endpoint is defined by the OAuth2 spec. This endpoint is responsible for authentication and obtaining consent from the end user. Additional details can be found here. OIDC requests to this endpoint are composed of parameters that are defined in the OAuth2 and OIDC specs. See the discussion of the Authentication Flows below.

Token Endpoint

The Token Endpoint is defined by the OAuth2 spec as well. This endpoint is responsible for allowing a Client to exchange an authorization code or client identifier & client secret for an Access Token. Additional details can be found here. OIDC requests to this endpoint are composed of parameters that are defined in the OIDC specs. See the discussion of the Authentication Flows below.

UserInfo Endpoint

The UserInfo Endpoint is defined by the OIDC spec. It allows an actor (Client or Resource Server) to request additional claims.

To retrieve the standard set of claims from the UserInfo Endpoint of the OpenID Provider, a request similar to the following should be sent (where the token in the Authorization header is the Access Token that was presented to the Client).

GET /oidc/userinfo HTTP/1.1
Host: server.example.com
Authorization: Bearer PHNhbWxwOl…[omitted for brevity]…ZT4

The response would look something like this.

HTTP/1.1 200 OK
  Content-Type: application/json

  {
   "sub": "john.smith@levvel.io",
   "name": "John Smith",
   "given_name": "John",
   "family_name": "Smith",
   "preferred_username": "john.smith@levvel.io",
   "email": "john.smith@levvel.io"
  }

If the actor requests signing and/or encryption, then a JWT will be returned with appropriate application of the JWS and JWE specs.

Summary

This post has provided an introduction to OIDC concepts. In the next post, we’ll begin looking at the Authentication Flows defined by the OIDC Spec.

Questions? Comments? Post them below.