JSON Web Token (JWT) for OAuth Client Authorization Grants

JWT for OAuth Client Authorization Grants enables a client to send a signed JWT token to the OpenID Connect Provider in exchange for an OAuth 2.0 access token.

JWT for OAuth Client Authorization Grants is included in the openidConnectServer-1.0 feature. It enables a client to send a signed JWT token to the OpenID Connect Provider in exchange for an OAuth 2.0 access token.

An example usage scenario of this functionality might be a customer of an electric company who authorizes automatic monthly payments from an online bank. Assuming the electric company and the online bank have established a trusted relationship for the purposes of fulfilling such requests. The electric company can send a signed JWT Token with proper claims to the token endpoint URI of the OpenID Connect Provider that are configured for the online bank in order to request an OAuth 2.0 access token each month. The electric company can then use the access token to cash monthly payments from the online bank.

Portions of the JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants specification are supported for Liberty servers that are configured as OpenID Connect Providers. Users that want to support the JWT client functionality must do so by using their own application.

Authorized scopes

The OpenID Connect Client sends an HTTPS request with a JWT to the token endpoint of the OpenID Connect Provider to request an access token. During this process, the user sees no consent form for authorizing the use of scopes. The JWT handler will handle the authorized scopes that are based on the following criteria:

  1. If no scope parameter is specified in the request, the OpenID Connect Provider will not specify any scopes in the access token.
  2. When an OpenID Connect Client is qualified as an autoAuthorized client in the OpenID Connect Provider configuration, any scope that is specified by the client in the request is specified in the scope list of the access token.
  3. When an OpenID Connect Client is not qualified as an autoAuthorized client, the scope that is included in the request needs to be filtered by the list of scopes in the client configuration and must also be specified in the preAuthorizedScope list. If a scope in the HTTPS request is in the scope and preAuthorizeScope list of the client configuration, the scope can be specified in the scope list of the access token.

When a client is not qualified as an autoAuthorized client, scopes that can be included in the scope list of the access token must be properly configured in the client configuration. The scope must be included in the values for the scope and preAuthorizedScope attributes in the client configuration for the OpenID Connect Provider. In the example that is shown, the scopes profile and email are specified in the scope list of the access token because both are included in the scope and preAuthorizedScope value list. If a scope is not listed in the scope attribute of the client configuration, it is omitted from the scope list of the access token. If a scope is listed in the scope attribute but is not included in the preAuthorizedScope list within the client configuration, the authorization request triggers an invalid_grant error in the response from the OpenID Connect Provider.


<openidConnectProvider id="OidcConfigSample" oauthProviderRef="OAuthConfigSample" />
<oauthProvider id="OAuthConfigSample" ... >
        ...
        <localStore>            
            <client name="client01" secret="{xor}..."
                    displayname="client01"
                    scope="profile email phone"
                    preAuthorizedScope="profile email"
                    enabled="true"/>
            ...
        </localStore>
    </oauthProvider>

Claims in a JSON Web Token

A valid JSON Web Token must be signed. A Liberty server that is configured as an OpenID Connect Provider only supports HMAC-SHA256 as the token signing algorithm. The signing key for each OpenID Connect Client is the secret attribute in the client configuration of the OpenID Connect Provider. In the example that is shown, the signing key that is used would be "{xor}LDo8LTor".

<client name="client01" displayname="client01" secret="{xor}LDo8LTor" ... />

The OpenID Connect Provider also verifies the following claims in a JWT:

'iss' (issuer)
This claim is required in a JWT. The iss claim must match the name attribute or the redirect attribute of the client configuration in the OpenID Connect Provider. In the following example, the iss claim must match either client01 or http://op201406.ibm.com:8010/oauthclient/redirect.jsp.
<client name="client01" redirect="http://op201406.ibm.com:8010/oauthclient/redirect.jsp" scope="openid profile email" ... />
'sub' (subject)
This claim is required in a JWT. The value of the subject must be a valid user name in the user registry of the OpenID Connect Provider server.
'aud' (audience)
This claim is required in a JWT. The value of the audience claim is the name of the issuerIdentifier when the issuerIdentifier attribute is specified in the openidConnectProvider configuration. If the issuerIdentifier attribute is not specified in the openidConnectProvider configuration, the audience must be the token endpoint URI of the OpenID Connect Provider. In the following example, the value of the audience claim would be "OpenIDConnectProviderID1".
<openidConnectProvider id="OidcConfigSample" oauthProviderRef="OAuthConfigSample" issuerIdentifier="OpenIDConnectProviderID1" />
'exp' (expiration)
This claim is required in a JWT and limits the time window that the JWT can be used. The OpenID Connect Provider verifies the exp against its system clock, plus some allowable clock skew.
'nbf' (not before)
This is an optional claim. When present, the token is only valid after the time specified by this claim. The OpenID Connect Provider verifies this time against its system clock, plus some allowable clock skew.
'iat' (issued at)
By default, this is an optional claim. However, if the iatRequired attribute of thejwtGrantType element is set to true, then all JWTs are required to contain the iat claim. When present, the iat claim indicates the time at which the JWT was issued. A JWT cannot be issued longer than the maxTokenLifetime.
'jti' (JWT ID)
This is an optional claim and is the unique identifier of a JWT Token. When present, the same JWT ID cannot be reused by an issuer. For example, if client01 issues a JWT whose jti is id6098364921, then no other JWT issued by client01 can have a jti value of id6098364921. A JWT with a jti claim identical to another JWT is considered to be a replay attack. Liberty servers that are configured as OpenID Connect Providers set up a jti cache on the server. The size of the cache is specified by the maxJtiCacheSize in the jwtGrantType configuration. The jti IDs that are kept in the cache are checked against any new incoming jti ID. The jti IDs stored in the cache are not discarded unless the cache is full.

Submitting JSON Web Token requests

It is a best practice to use the HTTPS protocol instead of HTTP to submit a JWT request. The token endpoint of the OpenID Connect Provider is used for handling HTTPS JWT requests. To determine the token endpoint for the OpenID Connect Provider, see Invoking the Token Endpoint for OpenID Connect or OAuth endpoint URLs.

The request must contain the following parameters:

  • grant_type - The value of this parameter must be "urn:ietf:params:oauth:grant-type:jwt-bearer"
  • assertion - The value of this parameter must contain a single signed JWT Token.
  • scope - This parameter is optional. If scope is omitted, the access token that is returned does not contain any scopes. The scope values listed in the scope parameter are checked against the OpenID Connect Provider configuration. For more information, see the previous Authorized Scopes section.
  • client_id - The value of this parameter must match the name attribute in the client configuration of the OpenID Connect Provider.
  • client_secret - The value of this parameter must match the secret attribute in the client configuration of the OpenID Connect Provider.

An example HTTPS request:

POST /token.oauth2 HTTP/1.1
    Host: oidc.ibm.com
    Content-Type: application/x-www-form-utlencoded

    client_id=client01
    &client_secret=secret     
    &grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer     
    &assertion=eyJhbGc[---omitted---]kIn0.eyJpc[---ommitted---]A4fQ.MB6ZFlCsHg5MJ-weIHZYz6xgF1jdSZn7ErchHs8-8Rk     
    &scope=profile email

A Java example to create a signed JWT Token:

package com.ibm.sample;

import java.security.SignatureException;
import com.google.gson.JsonObject;
import net.oauth.jsontoken.crypto.HmacSHA256Signer;

import net.oauth.jsontoken.SystemClock;
import net.oauth.jsontoken.JsonToken;
import org.joda.time.Duration;
import org.joda.time.Instant;

public class SampleJWTToken {
        private static final Duration SKEW = Duration.standardMinutes(5);

        JsonToken jwtToken = null;
        String[] allPayloadKeys = { "iss", "sub", "aud", "exp", // required
                                    "nbf", "iat", "jti" }; // optional

        public SampleJWTToken(String clientId, 
                              String keyId,
                              String signKey,
                              String audience, 
                              String subject, // user
                              String jtiId) throws Exception { // InvalidKeyException

                byte[] hs256Key = signKey.getBytes();
                HmacSHA256Signer hmacSha256Signer = new HmacSHA256Signer(
                                clientId, keyId, hs256Key);
                // _rsaSha256Signer = new RsaSHA256Signer(clientId, _keyId,
                //                                        _privateKey);
                SystemClock clock = new SystemClock(SKEW);
                jwtToken = new JsonToken(hmacSha256Signer, clock);
                JsonObject headerObj = jwtToken.getHeader();
                JsonObject payloadObj = jwtToken.getPayloadAsJsonObject();

                headerObj.addProperty("alg", "HS256");

                Instant instantExp = clock.now().plus(600000); // 10 minutes
                jwtToken.setExpiration(instantExp);
                jwtToken.setAudience(audience);
                payloadObj.addProperty("iss", clientId);
                payloadObj.addProperty("sub", subject);

                // optional
                payloadObj.addProperty("jti", jtiId);
                jwtToken.setIssuedAt(clock.now()); // issued at time
        }

        public String getJWTTokenString() throws Exception {
                String signedAndSerializedString = null;
                try {
                        signedAndSerializedString = jwtToken.serializeAndSign();
                } catch (SignatureException e) {
                        throw e;
                }
                return signedAndSerializedString;
        }
}