Password grant handler SPI

1. Introduction

The OAuth 2.0 framework defines several methods (called grants) for clients to obtain access tokens from an authorisation server, and one of them is the resource owner password credentials grant.

This grant relies on the client to obtain the user's password, which it then submits to the Connect2id server for verification. The server can potentially process a second authentication factor, such as a verification code delivered by SMS or email (this is a custom Connect2id server feature). If the credentials are valid the user is deemed authenticated and having consented the token request. The client then receives from the Connect2id server an access token and optional refresh token.

The password grant has better security properties than storing the user credentials in the client. If the client device is compromised or stolen, the username and password will not be exposed, and the tokens issued to the client can be simply revoked.

Do not use the password grant unless the client application is trusted, for example where

  • the client app is published by the same company that operates the Connect2id server and has issued the user's credentials;
  • the client app is part of the device OS.

Using the password grant with a third-party client app must never be permitted, as this will expose the user's credentials and can lead to impersonation.

1.1 Example token request with a password grant

The client submits the end-user's name and password to the token endpoint of the authorisation server, while also providing HTTP basic authentication for itself:

POST /token HTTP/1.1
Host: c2id.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=alice&password=secret

The authorisation server checks the client credentials, then the username and password, and if they are valid, returns an access token (possibly together with a refresh token) that has a scope (read, write) and expiration determined by the server's policy rules:

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

{
  "access_token"  : "2YotnFZFEjr1zCsicMWpAA",
  "token_type"    : "Bearer",
  "expires_in"    : 3600,
  "refresh_token" : "tGzv3JOkF0XG5Qx2TlKWIA",
  "scope"         : "read write"
}

The client can then use the access token to make requests to the intended protected resource (e.g. web API).

The token request with a password grant is described in the core OAuth 2.0 spec, see RFC 6759, section 4.3.

1.2 Prerequisites

For a client to make a token request with the end-user's username and password it must be registered for the password grant. The client itself is authenticated with the client_id and client_secret credentials set during registration. The default client authentication method is HTTP basic, unless another one is specified. The client is also typically registered for a set of scope values that it may request.

1.3 Optional OpenID tokens

The Connect2id server, being an OpenID Connect provider (OP), can be set up to include an ID token with the token response (this is a non-standard feature).

1.4 How to pass an SMS or email verification code with the password grant

The Connect2id server provides a mechanism to include an SMS or email verification code along with the password. To achieve this specify a simple scheme to wrap the password and code together, then unwrap them in your grant handler.

For example, you can put the password and code into a JSON object that is Base64URL-encoded and passed as the password value:

  • Username: alice
  • Password: aZoa6nae
  • Verification code: 981204

Put the password and code into a JSON object:

{"p":"aZoa6nae","c":"981204"}

Encode into a Base64URL string:

eyJwIjoiYVpvYTZuYWUiLCJjIjoiOTgxMjA0In0

The resulting password grant request:

POST /token HTTP/1.1
Host: server.c2id.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=alice&password=eyJwIjoiYVpvYTZuYWUiLCJjIjoiOTgxMjA0In0

1.5 Additional username and password encryption

To keep the username and password confidential while in transit between the client application and the grant handler where they are going to get checked, the credentials can be encrypted in the client application, and then decrypted in the handler. We recommend using public key encryption for that, where the public keys are published at an endpoint or included in the app package.

1.6 Further reading

Check out our article on handling password grants in the Connect2id server.

2. Password grant handler SPI

How does the Connect2id server process password grants?

  1. The client collects the user's credentials (username + password) and makes a request to the token endpoint of the Connect2id serer. If the client is confidential, it also submits the registered authentication credentials for itself (e.g. the client_id and client_secret for HTTP basic auth).

  2. The Connect2id server validates the client_id and its credentials (for a confidential client). On failure to authenticate an invalid_client error is returned.

  3. The Connect2id server then invokes the password grant handler plugin interface (SPI), which has 2 tasks to perform:

    1. Validate the submitted username and password with the appropriate user store. This may be an LDAP directory, SQL database, or external web service. If the credentials are bad, the handler must return an invalid_grant error.
    2. If the user credentials are valid, determine the token scope. This is up to the handler's policy, which may take resolved permission details of the end-user and the client metadata parameters as decision inputs. The grant handler can also set other token properties, such expiration, encoding, optional refresh token, etc.
  4. If the handler found the password grant to be valid, the Connect2id server creates the appropriate token(s), persists the authorisation (if it's long-lived), and returns an appropriate token response to the client.

3. Available implementations

3.1 Web-based handler

The Connect2id server comes with a ready SPI implementation which delegates processing of the password grant to a web service.

Benefits of the web service approach:

  • You can use any language or framework to implement the username / password verification and the authorisation logic, leveraging your existing IT resources.

  • You can update the handler logic without having to restart or otherwise affect the availability of the Connect2id server.

The web service interface is simple -- a single HTTP POST, using JSON to convey the request and response parameters.

The web-based handler connector and API are provided as an open source (Apache 2.0) package.

Git repohttps://bitbucket.org/connect2id/grant-handlers-web

There is also an example web service implementation available, which uses an LDAP directory to verify the username / password credentials and simple logic to determine the allowed scope values:

Git repohttps://bitbucket.org/connect2id/password-grant-web-api-example

3.1.1 Configuration

The Connect2id server is shipped with a base configuration for the password grant handler located in WEB-INF/passwordGrantHandlerWebAPI.properties. It can be replaced or overridden with Java system properties.

Content of the shipped handler configuration file, with explanation of the properties:

# Enables / disables the web-based resource owner password credentials grant
# handler. Disabled (false) by default.
op.grantHandler.password.webAPI.enable=true

# The endpoint URL of the web API.
op.grantHandler.password.webAPI.url=http://127.0.0.1:8080/password-grant-handler

# Names of custom token request parameters to include as top-level members in
# the handler request JSON object. The default value is none.
op.grantHandler.password.webAPI.customParams=

# Names of client metadata fields to include in the "client" JSON object which
# is part of handler request. The following client metadata fields are
# included by default: "scope", "application_type", "sector_identifier_uri",
# "subject_type", "default_max_age", "require_auth_time", "default_acr_values",
# "data"
op.grantHandler.password.webAPI.clientMetadata=

# Access token of type Bearer for the web API.
op.grantHandler.password.webAPI.apiAccessToken=ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

# The HTTP connect timeout, in milliseconds. The default value is zero, implies
# none or determined by the underlying HTTP client.
op.grantHandler.password.webAPI.connectTimeout=250

# The HTTP response read timeout, in milliseconds. The default value is zero,
# implies none or determined by the underlying HTTP client.
op.grantHandler.password.webAPI.readTimeout=500

To set up the handler the following minimal configuration must be provided:

  • Enabling of the handler.
  • The URL of the web service.
  • A long-lived bearer token to access the web service.

Example minimal configuration:

op.grantHandler.password.webAPI.enable=true
op.grantHandler.password.webAPI.url=https://login.example.com/password-grant-handler
op.grantHandler.password.webAPI.apiAccessToken=ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

To verify the handler loading and configuration check the server logs for lines with the OP7106 and PWWxxxx identifiers.

Example logged configuration:

INFO MAIN [main] [OP7106] Loaded and initialized password grant handler com.nimbusds.openid.connect.provider.spi.grants.handlers.web.password.PasswordGrantDelegator
INFO MAIN [main] [PWW0000] Password grant handler enabled: true
INFO MAIN [main] [PWW0001] Password grant handler: Endpoint URL: http://localhost:45021
INFO MAIN [main] [PWW0002] Password grant handler: Accepted custom token request parameters: []
INFO MAIN [main] [PWW0005] Password grant handler: Included client metadata parameters: [sector_identifier_uri, default_acr_values, subject_type, require_auth_time, data, application_type, default_max_age, scope]
INFO MAIN [main] [PWW0003] Password grant handler: HTTP connect timeout: 150 ms
INFO MAIN [main] [PWW0004] Password grant handler: HTTP read timeout: 250 ms

3.1.2 Web API

For each access token request with a password grant the Connect2id server will identify or authenticate the client according to its type (public or confidential) and check whether the client registration permits use of the grant. Only then will the server call the SPI (the connector to the web service) to process the grant.

The web service is expected to perform the following:

  • Check the username / password, e.g. by making a connection to an LDAP directory or some other database.

  • On success determine the authorisation scope. The logic for that can use the resolved user ID, the requested scope (if any), the registered client ID and metadata as inputs.

The web service interface is specified as follows:

HTTP POST

Header parameters:

  • Authorization The configured bearer access token for the web service (see op.grantHandler.password.webAPI.apiAccessToken).

  • Content-Type Set to application/json.

  • Issuer The issuer URL. Since v12.16.

Body:

  • A JSON object with the following members:

    • username {string} The username, as specified in the access token request.

    • password {string} The user password, as specified in the access token request.

    • [ scope ] {string array} The requested scope values, as specified in the access token request, empty array or omitted if none.

    • [ resources ] {string array} The requested target resource URI(s), omitted if none. Since v12.16.

    • client {object} JSON object containing the client_id and selected client metadata according to the op.grantHandler.password.webAPI.clientMetadata configuration. A boolean confidential parameter is also included, to indicate whether the client is confidential or public.

    • [ ... ] {string} Other custom token request parameters according to the op.grantHandler.password.webAPI.customParams configuration. Since v12.16.

Success:

  • Code: 200

  • Content-Type: application/json

  • Body: {object} A JSON with object with the following authorisation properties:

    • 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. Will be deprecated, use the access token property below!

    • [ long_lived = false ] {true|false} Controls the authorisation lifetime: true for a long-lived authorisation (implies persistence), false for a short-lived one (default value).

    • [ access_token ] {object} Optional access token settings:

      • [ lifetime = 0 ] {integer} The access token lifetime in seconds. If zero or omitted defaults to the configured access token lifetime.
      • [ encoding = "SELF_CONTAINED" ] {"SELF_CONTAINED"|"IDENTIFIER"} The access token encoding. If omitted defaults to self-contained (JWT-encoded).
      • [ audience ] {string array} Optional explicit list of audiences for the access token, omitted if none.
      • [ sub_type = "PUBLIC" ] {"PUBLIC"|"PAIRWISE"} The access token subject type. If PAIRWISE the access token with be issued with a pairwise subject identifier. This requires the audience to be set (if multiple audience values are set the first in the list will used to compute the pairwise identifier). Defaults to PUBLIC.
      • [ encrypt = false ] {true|false} Encryption flag. Applies only to self-contained (JWT-encoded) access tokens. If true the JWT will be encrypted with a shared AES key after signing for confidentiality.
    • [ id_token ] {object} Optional ID token settings:

      • [ issue = false ] {true|false} Enables / disables ID token issue. Defaults to false if omitted.
      • [ lifetime = 0 ] {integer} The ID token lifetime in seconds. If zero or omitted defaults to the configured ID token lifetime.
      • [ impersonated_sub ] {string} Specifies an impersonated subject for the issued ID token. May be used to enable privileged users to log in with a different identity. The original subject will be indicated by a custom authz_sub ID token claim.
    • [ refresh_token ] {object} Optional refresh token settings:

      • [ issue = true ] {true|false} Enables / disable refresh token issue. Applies only to long-lived (persisted) authorisations. If true a refresh token will be issued along with the access token. If false no access token will be issued. Defaults to false if omitted.
      • [ lifetime ] {integer} The refresh token lifetime in seconds. Zero means permanent (no expiration). If omitted defaults to the configured refresh token lifetime. If a client tries to refresh an access token and the refresh token has expired, the token endpoint of the Connect2id server will return an invalid_grant error. This can serve as a signal to the client to repeat the authentication request.
    • [ auth_time ] {integer} The time of the subject (end-user) 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 names of consented OpenID claims.

      Special keywords and prefixes:

      id_token: - For a non-requested claim, the "id_token:" prefix to its name will cause the claim to be delivered in the issued ID token instead of at the UserInfo endpoint (default location for all non-requested claims).

      access_token: - For a non-requested claim, this prefix will cause the claim to be delivered in the issued access token. If the access token is self-contained (JWT) the claim will be added at the top-level. If the access token is identifier-based the claim will appear at the top-level in the token introspection response.

      access_token:uip: - For a non-requested claim, this prefix will cause the claim to be merged into the top-level "uip" (optional preset UserInfo claims) JSON object claim of the access token.

      access_token:dat: - For a non-requested claim, this prefix will cause the claim to be merged into the top-level "dat" (optional data) JSON object claim of the access token.

      verified: - If Identity Assurance is enabled indicates a verified claim. Must be after any other prefixes.

    • [ claims_locales ] {string array} Optional array of the claims locales, omitted if not specified.

    • [ claims_data ] {object} Optional data to be passed in the request to retrieve the consented OpenID claims from the configured source(s). The claims data will be included in a "cld" (claims data) field in the issued access token(s) and in the long-lived authorisations if the consent is persisted. If the claims data must be kept confidential from the client either an identifier access token encoding must be chosen or if a self-contained (JWT) access token is chosen it must be additionally encrypted. An AdvancedClaimsSource SPI implementation can retrieve the claims data JSON object by a call to the ClaimsSourceRequestContext.getClaimsData method.

    • [ preset_claims ] {object} Optional JSON object specifying additional preset OpenID claims to include in the ID token and / or the UserInfo response:

      • [ id_token ] {object} Preset claims to include in the ID token, omitted or empty JSON object if none.

      • [ userinfo ] {object} Preset claims to include in the UserInfo response, omitted or empty JSON object if none.

    • [ claims_transport = "USERINFO" ] {"USERINFO"|"ID_TOKEN"} The preferred claims transport, defaults to USERINFO if omitted.

    • [ data ] {object} Optional additional information to be stored in the dat field of the authorisation record and self-contained (JWT-encoded) access tokens.

Errors:

Example request from a confidential client app with ID 123, citing the username, password and scope parameters from the access token request:

POST /password-grant-handler HTTP/1.1
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json
Issuer: https://c2id.com

{
  "username" : "bob",
  "password" : "secret",
  "scope"    : [ "openid", "email", "profile" ],
  "client"   : { "client_id"        : "123",
                 "confidential"     : true,
                 "application_type" : "native" }
}

Example response for a successful authorisation (an ID token is also to be issued in addition to the access token):

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

{
  "sub"      : "ecb51d49-026e-42d7-972d-03b5d0ee20e4",
  "scope"    : [ "openid", "email", "profile" ],
  "id_token" : { "issue" : true }
}

Example response indicating bad username / password credentials:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error"             : "invalid_grant",
  "error_description" : "Bad username/password"
}

4. How to develop your own password grant handler

First, read our general guide for developing, annotating and packaging an SPI-based plugin.

The connector must implement the PasswordGrantHandler SPI defined in the Connect2id server SDK:

Git repohttps://bitbucket.org/connect2id/server-sdk

If the Connect2id server detects an SPI implementation at startup it will log its loading under OP7106:

INFO MAIN [main] [OP7106] Loaded and initialized password grant handler com.nimbusds.openid.connect.provider.spi.grants.handlers.web.password.PasswordGrantDelegator

Note, the Connect2id server can load multiple password grant handlers at startup, but only one may be enabled at a time.