Nimbus JOSE + JWT 3.0, redesigned and with AES key wrap support

2014-08-20

Thanks to Melisa Halsband, a contributor from CertiVox UK, the Nimbus library can now handle JSON Web Tokens (JWT) and other objects encrypted with a shared key using the AES or AES/GCM key wrap algorithm.

These are the JWA identifiers for the newly added algorithms:

  • For AES key wrap encryption: A128KW, A192KW and A256KW
  • For AES GCM key wrap encryption: A128CGMKW, A192CGMKW and A256CGMKW

Support of AES key wrap necessitated a number of braking changes to the JOSE header and object classes. That cleared the way for a number of other significant changes in the new 3.0 release, all of which we hope you'll find useful :)

Simplified immutable headers

The plain / JWS / JWE header classes are now simplified and completely immutable, which makes for safer development. A builder is available to help you construct headers where you need to specify additional parameters than the mandatory alg and enc:

JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).
                   keyID("123").
                   contentType("text/plain").
                   customParam("exp", new Date().getTime()).
                   build();

To facilitate AES key wrap the JWE header was given direct support for the iv and tag parameter. We also added support for the new x4t#S256 (X.509 certificate SHA-256 thumbprint) header parameter added in draft 27 of the JOSE specs.

AES key wrap encryption

AES key wrap is useful in situations when you want to encrypt a message using a shared secret key, e.g. to ensure confidentiality of ID tokens in OpenID Connect where the client application has been issued with a simple client secret at registration.

Note that the key length must match the one required by the algorithm:

  • 128 bit key for A128KW
  • 192 bit key for A192KW
  • 256 bit key for A256KW
// Create JWE object
JWEHeader header = new JWEHeader(JWEAlgorithm.A128KW, EncryptionMethod.A128CBC_HS256);
Payload payload = new Payload("Hello world!");      
JWEObject jweObject = new JWEObject(header, payload);

// Encrypt with secret key
JWEEncrypter encrypter = new AESEncrypter(secretKey);
jweObject.encrypt(encrypter);

// Output JWE string
jweObject.serialize();

// ...


// Parse JWE string on recipient side
jweObject = JWEObject.parse(jweString);

// Decrypt with same secret key
JWEDecrypter decrypter = new AESDecrypter(key128);
jweObject.decrypt(decrypter);

// Get the decrypted payload
assertEquals("Hello world!", jweObject.getPayload().toString());

Encryption with AES GCM key wrap is similar, you just need to specify A128GCMKW as the JWE algorithm instead.

Serialising single-valued JWT audience (aud) claims

A developer complained that some JWT libraries cannot handle JWT audience (aud) values that are JSON arrays, even in the simple case (array with a single audience). To enable interop with such software we decided to modify the JWTClaimsSet class, so that single-valued aud claims are now output as a string.

JWT claims set with a single aud:

{
 "iss": "https://c2id.com",
 "sub": "alice",
 "aud": "http://app.example.com"
}

JWT claims set with multiple aud values:

{
 "iss": "https://c2id.com",
 "sub": "alice",
 "aud": [ "http://app1.example.com", "http://app2.example.com" ] 
}

Java 7 is now the minimum requirement

With the new 3.0 release you will now need Java 7 to build and run the library. Java 6 is no longer officially supported as of February 2013, so we decided to drop support for it too, and start enjoying the language features Java 7 has to offer (and are already eyeing Java 8, but we'll have to be patient ;)

Matching IETF specifications

From the JOSE WG:

From the OAuth WG:

Download

The Maven Dependency for the new 3.0 release:

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>3.0</version>
</dependency>

For other methods check out the downloads page.

Using OpenID Connect to make assertions about end-users

2014-08-03

OpenID Connect provides an excellent mechanism for issuing assertions, or claims (digitally signed statements) about end-users. Such assertions can for example be the end-user's name, contact details with verification status, organisational role, or reputation scores.

Example claims about an end-user, encoded in a simple JSON object:

{
   "sub"            : "alice",
   "name"           : "Alice Adams",
   "email"          : "[email protected]",
   "email_verified" : true,
   "reputation"     : 1500,
   "location"       : "Wonderland"
   "avatar"         : "http://example.com/alice.jpg"
 }

How claims are communicated in OpenID Connect

An OpenID Connect provider can communicate claims to client applications in two different ways:

  1. ID token
    The ID token is a signed object that asserts the user's identity, issuer and authentication time / strength / methods. The OpenID Connect provider may include additional claims in the ID token, such as geo-location, personal details, organisational roles, etc. The ID token is encoded as a JSON Web Token (JWT) signed by the provider and may optionally be encrypted for confidentiality. The only limitation is that ID tokens should not exceed 2000 characters in length as certain protocol flows (OpenID Connect is based on the OAuth 2.0 framework) return the ID token by means of URL redirection.

  2. OAuth 2.0 access token
    An OpenID Connect provider can also return an OAuth 2.0 bearer access token to let the client application access a UserInfo endpoint to release consented information about the end-user. That information is also represented as a JSON object and can be much larger in size as it doesn't suffer the ID token's limitation. In contrast to the ID token, the access token may also allow access to the UserInfo when the end-user is offline, by extending the access token's lifetime or by providing the client application with a refresh token.

Example ID token payload (after JWT decoding and signature verification):

{
  "iss"       : "https://openid.wonderland.net",
  "sub"       : "alice",
  "aud"       : "s6BhdRkqt3",
  "nonce"     : "n-0S6_WzA2Mj",
  "exp"       : 1311281970,
  "iat"       : 1311280970,
  "auth_time" : 1311280969,
  "acr"       : "urn:acr:2fa",
  "name"      : "Alice Adams",
  "email"     : "[email protected]",
  "roles"     : [ "admin", "audit" ]
}

Building a business model on premium claims

The Connect2id server can easily support business models where money is charged for providing claims of value about end-users to interested client applications. The server can handle multiple tiers of client applications, e.g. a fremium tier that can only receive basic assertions about the end-user and a premium tier with access to value-added assertions.

Differentiating between fremium and premium application subscriptions is as simple as setting a designated value in the scope field of the client application's registration details stored by the Connect2id server.

Pick your own claims source

The Connect2id server can pull assertions (claims) from any data source, supporting aggregation from multiple sources if required.

Example claims sources:

  • LDAP directories
  • SQL or NoSQL database
  • SCIM web service
  • HR system
  • XML file

We have defined a simple Java SPI for that. An LDAP claims source connector is provided out of the box that should satisfy most enterprises looking to provide assertions about their users to applications.

Experimental OAuth 2.0 password grant support in the Connect2id server

2014-07-31

The next major 2.0 release of the Connect2id server for single-sign on (SSO) with OpenID Connect and access management with OAuth 2.0 will be able to handle resource owner password credential grants, as defined in section 4.3 of the core OAuth 2.0 spec (RFC 6749).

Test drive the password grant

To test drive the password grant you will need one of the recent 2.0 snapshots of the Connect2id server. Install the package as usual, then fire up your favourite REST or web client.

The client registry of the Connect2id server comes with a ready record for a client that has been given the permission to make password grant requests. The record contains a bunch of metadata about the client, and also the client ID and secret required to authenticate the requests that will come from it:

cliend_id = 000123
client_secret = 7wKJNYFaKKg4FxUdi8_R75GGYsiWezvAbcdN1uSumE4
token_endpoint_auth_method = client_secret_basic

The actual access token request with a resource owner password credentials grant is explained succinctly in the OAuth 2.0 spec.

You'll need the following details to compose it:

This is how the request should look like:

POST /token HTTP/1.1
Host: localhost:8080
Authorization: Basic MDAwMTIzOjd3S0pOWUZhS0tnNEZ4VWRpOF9SNzVHR1lzaVdlenZBYmNkTjF1U3VtRTQ=
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=bob&password=secret

With curl:

curl --user 000123:7wKJNYFaKKg4FxUdi8_R75GGYsiWezvAbcdN1uSumE4 \
     --data "grant_type=password&username=bob&password=secret" 
     http://localhost:8080/c2id/token

The access token response should then look like similar to this:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
 "token_type"    : "Bearer",
 "access_token"  : "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJzY3AiOlsib3BlbmlkIiw...",
 "expires_in"    : 3600,
 "scope"         : "openid email profile offline_access",
 "refresh_token" : "Ym9i.MDAwMTIz.v9oy8xX5mpf9gWlxn5ijsg",
 "id_token"      : "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJleHAiOjE0MDY4MzUyNjk..."
}

The ID token will contain the resolved identity of the end-user, as a signed JSON Web Token (JWT), while the access token can be used to retrieve the scoped claims about the end-user from the UserInfo endpoint. The Connect2id server is also an OAuth 2.0 authorisation server, so access tokens produced by it can be used with other arbitrary protected resources as well.

Simple and flexible handling of password grant requests

How does the Connect2id server handle password grant requests under the hood?

We designed a Java SPI and a web API to enable customers to plug in their own business logic to determine what scope the issued access token must be given, based on the following two inputs passed from the Connect2id server:

  1. The client_id and the registered client metadata of the application, after the client authentication is validated by the server.

  2. The end-user identity, provided the username and password credentials are successfully validated.

Based on these two inputs, an enterprise can devise arbitrary authorisation scripts to arrive at a scope value (or deny the request altogether), so that the Connect2id server can then proceed to issue the matching access token for it.

When to use the Java SPI?

  • If you're comfortable coding the business logic in Java, and don't expect that logic to change often. The Connect2id server will load it as a JAR module startup and call it for each password grant request after the client credentials are successfully authenticated.

When to use the web API?

  • If you wish to code the business logic in some other language, or want to be able to change it frequently. The main advantage of using the web API to integrate the authorisation logic is that it can then be updated on the fly, and doesn't require the Connect2id server to be restarted, which streamlines DevOps significantly.

Configuration and use of the web API for handling password grants

To set up your web handler for processing password grants you need to provide the Connect2id server with the following bits of information:

  • The URL of your web handler.

  • A long-lived bearer access token for the web service.

  • HTTP connect and read timeouts.

These should be saved in the WEB-INF/passwordGrantHandlerWebAPI.properties configuration file of the Connect2id server.

For example:

op.grantHandler.password.webAPI.enable = true
op.grantHandler.password.webAPI.url = https://c2id.com/protected/password-grant-handler
op.grantHandler.password.webAPI.apiAccessToken = ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
op.grantHandler.password.webAPI.connectTimeout = 250
op.grantHandler.password.webAPI.readTimeout = 500

For each access token request which client is correctly authenticated the Connect2id server will post to the specified URL a JSON object with the following members:

  • username {string} The username, as provided by the client application.
  • password {string} The user password, as provided by the client application.
  • [ scope ] {string array} The scope values requested by the client application, empty array or omitted if none.
  • client {object} JSON object containing the client_id and registered metadata of the client application, as specified in the OpenID Connect dynamic client registration spec.

Example request to the grant handler service:

POST /password-grant-handler/authz.json HTTP/1.1
Host: localhost:8080
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "username" : "bob",
  "password" : "secret",
  "scope"    : [ "openid", "email", "profile", "offline_access" ],
  "client"   : { "client_id"                  : "000123",
                 "client_name"                : "My Test App",
                 "grant_types"                : [ "password" ],
                 "response_types"             : [],
                 "token_endpoint_auth_method" : "client_secret_basic",
                 "application_type"           : "web" }
}

The handler should verify the web API access token (included in the Authorization header), check the resource owner credentials (e.g. by making a call to an LdapAuth service, and then finally run whatever authorisation logic is required to return a set of scope values and other authorisation info back to the Connect2id server.

The authorisation info is communicated back to the Connect2id server with a JSON object where only two details are required - the identifier of the authenticated subject (end-user) and the authorised scope values. The remaining members are optional and are used to tailor the released authorisation.

  • sub {string} The identifier of the authenticated subject (end-user).
  • scope {string array} An array of one or more authorised scope values for the access token. May be different from the originally requested scope values.
  • [ audience ] {string array} Optional explicit list of audiences for the access token, omitted if none.
  • [ long_lived = true ] {true|false} Controls the authorisation lifetime: true for a long-lived authorisation (implies persistence), false for a short-lived one.
  • [ access_token ] {object} Optional JSON object with settings for the access token.
    • [ lifetime ] {integer} The preferred access token lifetime, in seconds. If omitted defaults to the configured lifetime.
    • [ encoding ] {"INTEGER"|"SELF_CONTAINED"} The preferred access token encoding. If omitted defaults to the configured encoding.
  • [ issue_refresh_token = true ] Controls the refresh token issue: true to allow refresh token issue (requires a long-lived authorisation), false to only issue an access token.
  • [ issue_id_token = false ] Controls the ID token issue: true to issue an ID token, false to omit it.
  • [ auth_time ] {integer} The time of the subject authentication, as number of seconds since the Unix epoch. If omitted it will be set to now. Applies only if an ID token is issued.
  • [ acr ] {string} The Authentication Context Class Reference (ACR), omitted if not specified. Applies only if an ID token is issued.
  • [ amr ] {string array} The Authentication Methods Reference (AMR) list, omitted if not specified. Applies only if an ID token is issued.
  • [ claims ] {string array} Optional array of the authorised OpenID Connect UserInfo and other claims, omitted if none.
  • [ preset_claims ] {object} Optional JSON object specifying preset OpenID Connect claims to return with the ID token and at the UserInfo endpoint:
    • [ id_token ] {object} Additional or preset claims to be included in the ID token, omitted if none.
    • [ userinfo ] {object} Additional or preset claims to be included in the UserInfo response, omitted if none.
  • [ claims_transport = "USERINFO" ] {"USERINFO"|"ID_TOKEN"} The preferred claims transport, defaults to UserInfo if omitted.

Example authorisation response from the password grant handler. The Connect2id server will use its details to generate an access and refresh token for user bob with the cited scope values, and will return the tokens to the client application in a standard access token response.

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

{
  "sub"                : "bob",
  "scope"              : [ "openid", "email", "profile", "offline_access" ],
  "issue_refresh_token : false,
  "issue_id_token"     : true
}

How to register new clients for the password grant

To register client apps that can make access token requests with a password grant there are only two required parameters, all others are optional:

POST /client-reg HTTP/1.1
Content-Type: application/json

{
  "grant_types"    : [ "password" ],
  "response_types" : [ ]
}

To change an existing client registration to support password grants, just make sure password is added to the list of the grant_types.

Questions? Feel free to post a comment below, or get in touch with Connect2id support.