Experimental OAuth 2.0 password grant support in the Connect2id server

Posted 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.