This document provides a developer overview of the OpenID Connect 1.0 protocol (OIDC) and provides instructions for an Application Developer to implement OpenID Connect with PingFederate. Two walkthroughs are provided to demonstrate the OpenID Connect Basic Client Profile and the OpenID Connect Implicit Client Profile.
This is targeted to developers, however the content will be relevant for infrastructure owners to understand the OpenID Connect concepts. Explanations and code examples are provided for "quick win" integration efforts. As such they are incomplete and meant to complement existing documentation and specifications.
This document assumes a basic familiarity with the OpenID Connect 1.0 protocol and the OAuth 2.0 protocol. For more information about OAuth 2.0 and OpenID Connect 1.0, refer to:
Note: This document explains a number of manual processes to request and validate the OAuth and OpenID Connect tokens. While the interactions are simple, PingFederate is compatible with many 3rd party OAuth and OpenID Connect client libraries that may simplify development effort.
The OpenID Connect protocol extends the OAuth 2.0 protocol to add an authentication and identity layer for application developers. Where OAuth 2.0 provides the application developer with security tokens to be able to call back-end resources on behalf of an end-user; OpenID Connect provides the application with information about the end-user, the context of their authentication, and access to their profile information.
Two new concepts are introduced on top of the OAuth 2.0 authorization framework:
OpenID Connect uses the same actors and processes as OAuth 2.0 to get the ID token, and protects the UserInfo endpoint with the OAuth 2.0 framework.
There are three main actions an application developer needs to handle to implement OpenID Connect:
The ID token is a token used to identify an end-user to the client application and to provide data around the context of that authentication.
An ID token will be in the JSON Web Token (JWT) format. In most cases the ID token will be signed according to JSON Web Signing (JWS) specifications, however depending on the client profile used the verification of this signature may be optional.
Note: When the id_token is received from the token endpoint via a secure transport channel (i.e. via the Authorization Code grant type) the verification of the digital signature is optional.
The id_token JWT consists of three components, a header, a payload and the digital signature. Following the JSON Web Token (JWT) standard, these three sections are Base64url encoded and separated by periods (.).
Note: JWT and OpenID Connect assume base64url encoding/decoding. This is slightly different than regular base64 encoding. Refer to RFC4648 for specifics regarding Base64 vs Base64 URL safe encoding.
The following example describes how to manually parse a sample ID token provided below:
Note: It is strongly recommended to make use of common libraries for JWT and JWS processing to avoid introducing implementation specific bugs.
The above JWT token is first split by periods (.) into three components:
Contains the algorithm and a reference to the appropriate public key if applicable:
The second component contains the payload which contains claims relating to the authentication and identification of the user. The payload of the above example is decoded as follows:
The following claims you can expect in an id_token and can use to determine if the authentication by the user was sufficient to grant them access to the application. (Refer to the OpenID Connect specifications to additional details on these attributes):
|iss||Issuer of the id_token|
|sub||Subject of the id_token (ie the end-user’s username)|
|aud||Audience for the id_token (must match the client_id of the application)|
|exp||Time the id_token is set to expire (UTC, Unix Epoch time)|
|iat||Timestamp when the id_token was issued (UTC, Unix Epoch time)|
|auth_time||Time the end-user authenticated (UTC, Unix Epoch time)|
|nonce||Nonce value supplied during the authentication request (REQUIRED for implicit flow)|
|acr||Authentication context reference used to authenticate the user|
|at_hash||Hash of the OAuth2 access token when used with Implicit profile|
|c_hash||Hash of the OAuth2 authorization code when used with the hybrid profile|
Base64 URL encoded 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.
The validation of the ID token includes evaluating both the payload and the digital signature.
The ID token represents an authenticated user’s session. As such the token must be validate before an application can trust the contents of the ID token. For example, if a malicious attacker replayed a user’s id_token that they had captured earlier the application should detect that the token has been replayed or was used after it had expired and deny the authentication.
Refer to the OpenID Connect specifications for more information on security concerns. The specifications also include guidelines for validating an ID token (Core specification section 18.104.22.168). The general process would be as follows:
|Step #||Test Summary|
|1||Decrypt the token (if encrypted)|
|2||Verify the issuer claim (iss) matches the OP issuer value|
|3||Verify the audience claim (aud) contains the OAuth2 client_id|
|4||If the token contain multiple audiences, then verify that an Authorized Party claim (azp) is present|
|5||If the azp claim is present, verify it matches the OAuth2 client_id|
|6, 7 & 8||Optionally verify the digital signature (required for implicit client profile) (see section 4.4)|
|9||Verify the current time is prior to the expiry claim (exp) time value|
|10||Client specific: Verify the token was issued within an acceptable timeframe (iat)|
|11||If the nonce claim (nonce) is present, verify that it matches the nonce passed in the authentication request
|12||Client specific: Verify the Authn Context Reference claim (acr) value is appropriate|
|13||Client specific: If the authentication time claim (auth_time) present, verify it is within an acceptable range|
|14||If the implicit client profile is used, verify that the access token hash claim (at_hash) matches the hash of the associated access_token|
Note: Signature validation is only required for tokens not received directly from the token endpoint (i.e. for the Implicit Client Profile). In other cases where the id_token is received directly by the client from the token endpoint over HTTPS, transport layer security should be sufficient to vouch for the integrity of the token.
The ID token is signed according to the JSON Web Signature (JWS) specification; algorithms used for signing are defined in the JSON Web Algorithm (JWA) specification. PingFederate 7.1 can support the following signing algorithms:
|"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: RS256 is the default signature algorithm.
The basic steps to verify a digital signature involve retrieving the appropriate key to use for the signature verification and then performing the cryptographic action to verify the 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):
Signature value to verify:
Note: The actual implementation of the signing algorithm used to validate the signature will be implementation specific. It is recommended to use a published library to perform the signature verification.
For symmetric key signature methods, the client secret value for the OAuth2 client is used as the shared symmetric key. For this reason the client secret defined for the OAuth2 client must be of a large enough length to accommodate the appropriate algorithm (i.e. for a SHA256 hash, the secret must be at least 256 bits – 32 ASCII characters).
Asymmetric signature methods require the application to know the corresponding public key. The public key can be distributed out-of-band or can be retrieved dynamically via the JSON Web Key Set (JWKS) endpoint as explained below:
1. Determine the signing algorithm (alg) and the key identifier (kid) from the JWT header. Using the sample JWT token above as an example, the following values are known:
|OpenID Connect issuer||https://localhost:9031|
|Signing algorithm (alg)||RS256|
|Key reference identifier (kid)||i0wnn|
2. Query the OpenID configuration URL for the location of the JWKS:
GET https://localhost:9031/.well-known/openid-configuration HTTP/1.1
this will result in a HTTP response containing the OpenID Connect configuration for the OpenID Connect Provider (OP) :
HTTP/1.1 200 OK
"code id_token","token id_token","code token id_token"],
3. Parse the JSON to retrieve the jwks_uri value (bolded above) and make a request to that endpoint, JSON Web Keystore (JWKS), to retrieve the public key for key identifier "i0wnn" and key type (kty) of RSA as the algorithm is RS256 that was used to sign the JWT:
GET https://localhost:9031/pf/JWKS HTTP/1.1
Which will return the JWKS for the issuer:
HTTP/1.1 200 OK
We now have the modulus (n) and the exponent (e) of the public key. This can be used to create the public key and validate the signature.
Note: The public key can be stored in secure storage (i.e. in the keychain) to be used for verification of the id_token when a user is offline.
In specific client profiles, a specific hash is included in the id_token to use to verify that the associated token was issued along with the id_token. For example, when using the implicit client profile, an at_hash value is included in the id_token that provides a means to verify that the access_token was issued along with the id_token.
The following example uses the id_token above and associated access_token to verify the at_hash id_token claim:
|OAuth 2.0 access_token||dNZX1hEZ9wBCzNL40Upu646bdzQA|
|left-most half value||wfgvmE9VxjAudsl9lc6TqA|
The OpenID Connect UserInfo endpoint is used by an application to retrieve profile information about the Identity that authenticated. Applications can use this endpoint to retrieve profile information, preferences and other user-specific information.
The OpenID Connect profile consists of two components:
Note: The user claims can also be presented inside the id_token to eliminate a call back during authentication time.
The UserInfo endpoint will present a set of claims based on the OAuth2 scopes presented in the authentication request.
OpenID Connect defines five scope values that map to a specific set of default claims. PingFederate allows you to extend the "profile" scope via the "OpenID Connect Policy Management" section of the administration console. Multiple policy sets can be created and associated on a per-client basis.
|Connect scope||Returned Claims|
|openid||None - Indicates this is an OpenID Connect request|
|profile||name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at, *custom attributes|
Note: If a scope is omitted (i.e. the "email" scope is not present), the claim "email" will not be present in the returned claims. For custom profile attributes, prefix the value to avoid clashing with the default claim names.
Note: If an OpenID Connect id_token is requested without an OAuth2 access token (i.e. when using the implicit “response_type = id_token” request), the claims will be returned in the id_token rather than the UserInfo endpoint.
Once the client application has authenticated a user and is in possession of an access token, the client can then make a request to the UserInfo endpoint to retrieve the requested attributes about a user. The request will include the access token presented using a method described in RFC6750.
The UserInfo endpoint provided by PingFederate is located at: https://<pingfederate_base_url>/idp/userinfo.openid
Note: The UserInfo endpoint can also be determined by querying the OpenID Connect configuration information endpoint:
An example HTTP client request to the UserInfo endpoint:
GET https://pf.company.com:9031/idp/userinfo.openid HTTP/1.1
Authorization: Bearer <access token>
A successful response will return a HTTP 200 OK response and the users claims in JSON format:
HTTP/1.1 200 OK
Before the client application can trust the values returned from the UserInfo endpoint (i.e. as a check for token substitution attack), the client must verify that the "sub" claim returned from the UserInfo endpoint request matches the subject from the id_token.