Password grant handler
1. Overview
The OAuth 2.0 framework defines several methods (called grants) for clients to obtain access tokens from an authorisation server. 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 is able to process a second authentication factor, such as an OTP or an email verification code (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 is then issued with an access token and an optional refresh token.
The password grant has better security than storing the user credentials in the client for basic authentication at web APIs. If the client device is compromised or stolen, the username and password are not exposed, instead only the tokens issued to the client are need to be revoked.
Do not use the password grant unless the client application is trusted, for example where
- the client app is published by the same entity that operates the Connect2id server and has issued the user’s credentials;
- the client app is part of the device OS.
The password grant must not be used with third-party client apps, 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 username and password to the token endpoint Connect2id server.
POST /token HTTP/1.1
Host: server.c2id.com
Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=123&username=alice&password=secret
The server checks the client_id
and whether it’s registered to use the
password grant, then the username and password, and if they are valid, returns
an access token (possibly with a refresh token) that has a scope and expiration
determined by the server:
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
The client must be
registered for the
password grant. It can be registered as a public
client or as a confidential
client with credentials, for instance client_secret
for client_secret_basic
authentication at the token endpoint. The client is also
typically registered with a set of scope
values that it may request.
1.3 Optional ID token
The Connect2id server, as an OpenID Connect provider (OP), can include an
optional ID token with the token response (this is a non-standard feature).
Use the openid
scope to signal this.
1.4 How to include an OTP, SMS or email verification code
The Connect2id server supports two methods for passing an additional string for the needs of a second authentication factor.
Via custom request parameter
Define a custom token request request parameter, for example
verification_code
, and look for it in the grant handler.
POST /token HTTP/1.1
Host: server.c2id.com
Content-Type: application/x-www-form-urlencoded
grant_type=password
&username=alice
&password=eyJwIjoiYVpvYTZuYWUiLCJjIjoiOTgxMjA0In0
&verification_code=981204
When using the web-based handler any custom parameters must be declared in the handler configuration:
op.grantHandler.password.webAPI.customParams=verification_code
Via custom encoding of the password parameter
Define a simple scheme to wrap the password and verification code, then unwrap them in your grant handler.
For example, 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 the JSON object string into a Base64URL string:
eyJwIjoiYVpvYTZuYWUiLCJjIjoiOTgxMjA0In0
The resulting password grant request:
POST /token HTTP/1.1
Host: server.c2id.com
Content-Type: application/x-www-form-urlencoded
grant_type=password
&client_id=123
&username=alice
&password=eyJwIjoiYVpvYTZuYWUiLCJjIjoiOTgxMjA0In0
1.5 Custom parameters in error responses
The Connect2id server supports the inclusion of custom parameters in error
responses, in
addition to the standard error
, error_description
and error_uri
.
Example invalid_grant
error with a custom request_id
parameter:
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error" : "invalid_grant",
"error_description" : "Invalid grant: Invalid username and / or password",
"request_id" : "AHC6AEGH"
}
1.6 Second authentication factor challenge
The ability to support custom parameters in token requests and error responses enables the implementation of two-step password grant flows with a challenge for a second authenticating factor.
For example, if the requested scope requires stronger assurance, the server
responds with a custom 2fa_required
error code to direct the client to obtain
an OTP verification code from the end-user.
The 2fa_state
captures the state, by storing the ID of the user whose
password was just checked and any other data necessary to complete the flow in
the grant handler once the verification code is obtained. The 2fa_state can be
a secure random key to a database record, or an HMAC-secured JWT (if the token
payload is allowed to be public). The expires_in
informs the client that the
challenge and the 2fa_state
expire in 120 seconds, after which the token
request must be restarted.
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error" : "2fa_required",
"error_description" : "Second factor authentication with OTP required",
"2fa_state" : "wooC3Be2tahmie8ua8chuT0Aizaathu8",
"expires_in" : 120
}
When the end-user enters the verification code into the client, the client
makes a new token request using the password grant, this time submitting a
verification_code
and the 2fa_state
from the previous step. The username
and password
were already submitted, hence no need to repeat them. Since they
are required parameters for a correctly formatted request a placeholder is used
for them and the values are going to be ignored.
POST /token HTTP/1.1
Host: server.c2id.com
Content-Type: application/x-www-form-urlencoded
grant_type=password
&client_id=123
&username=ignore
&password=ignore
&verification_code=981204
&2fa_state=wooC3Be2tahmie8ua8chuT0Aizaathu8
The handler validates the 2fa_state
and checks the verification_code
. On
success it proceeds as usual, returning an appropriate authorisation for the
client.
1.7 End-to-end username and password encryption
The submitted username and password can potentially be encrypted to the final destination where they are going to be processed, the grant handler. This ensures their confidentiality in respect to the Connect2id server and any web proxies and gateways involved in serving the HTTPS requests, which also includes the case when the grant handler is a dedicated web service.
The username and password credentials can be encrypted to a public key that belongs to the handler, which can be published at an endpoint or included in the client app package.
1.8 Further reading
Check out our original article on handling password grants in the Connect2id server.
2. Password grant handler SPI
How does the Connect2id server process password grants?
-
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.
client_id
andclient_secret
for HTTP basic authentication). -
The Connect2id server validates the
client_id
and its credentials (for a confidential client). On failure to authenticate an invalid_client error is returned. -
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, and can for instance be based on registered allowed scope values for the client. The grant handler can also set other token properties, such expiration, encoding, an optional refresh token, etc.
-
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 repo | https://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 repo | https://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 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 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.
-
On error set an appropriate
error
code and optionalerror_description
. Additional custom error parameters can be included.
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.
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.
-
client {object} JSON object containing the
client_id
and selected client metadata according to theop.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.
-
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} Long-lived authorisation flag. If
true
indicates a long-lived (persisted) authorisation. Iffalse
the authorisation is transient (not remembered). Defaults tofalse
. -
[ access_token ] {object} Optional access token settings:
-
[ lifetime = 0 ] {integer} The access token lifetime in seconds. If zero or omitted the default setting will apply.
-
[ encoding = “SELF_CONTAINED” ] {“SELF_CONTAINED”|“IDENTIFIER”} The access token encoding. If
SELF-CONTAINED
the token authorisation is encoded in the access token, as a signed and optionally encrypted JSON Web Token (JWT). IfIDENTIFIER
the issued access token is a secure random identifier; the underlying token authorisation can be retrieved at the introspection endpoint. Defaults toSELF_CONTAINED
. -
[ audience ] {string array} Optional parameter specifying the resource server audience for the issued access token. Should typically include the
client_id
(s) of the resource(s), but any other type of identifier, such as URI, is also accepted. Identifier-based tokens with a specified audience can be introspected only by resource servers whoseclient_id
is in the audience. -
[ encrypt = false ] {true|false} Encryption of the self-contained (JWT) access token after signing, to make its payload confidential from the client. The token is encrypted with the current active AES key using the configured JWE algorithm and method. The resource server(s) must be provisioned with a copy of the AES key to decrypt the token. Defaults to
false
, unless the client is an OpenID relying party registered for pairwise subjects in which case the token will be encrypted. The encryption can be opted out from if the access token subject is set to pairwise. -
[ 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 theaudience
to be set (if multiple audience values are set the first in the list will used to compute the pairwise identifier). Defaults toPUBLIC
.
-
-
[ refresh_token ] {object} Optional refresh token settings:
-
[ issue = true ] {true|false} Enables / disables refresh token issue. If
true
a refresh token will be issued (the client must also be registered for the refresh token grant. Iffalse
no refresh token will be issued. Defaults totrue
if omitted.Note, the
long_lived
consent parameter controls the encoding of the refresh token. -
[ lifetime ] {integer} The refresh token lifetime in seconds. Zero means permanent (no expiration). When omitted the default setting will apply. If a client tries to use an expired refresh token the token endpoint of the Connect2id server will return an invalid_grant error. This is a signal to the client to repeat the authorisation request.
-
[ rotate ] {true|false} If
true
the refresh token will be rotated at each use. When omitted the default setting will apply. Since v14.0.
-
-
[ 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. When zero or omitted the default setting will apply.
-
[ 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.
-
-
[ 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:
-
400 Bad Request
- With an encoded
invalid_grant
error indicates invalid resource owner credentials. See OAuth 2.0, section 5.2 on error responses for details. - With an encoded
invalid_scope
error indicates the requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner. See OAuth 2.0, section 5.2 on error responses for details. - Any other bad request.
- The error JSON object can use a custom
error
code and include additional members other thanerror_description
anderror_uri
. These will be conveyed verbatim in the token error response.
- With an encoded
-
401 Unauthorized – With an encoded bearer token error indicates the access token is invalid. See OAuth 2.0 Bearer Token, section 3.1 on error responses for details.
-
500 Internal Server Error – Indicates an internal grant processing error.
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"
}
Example response with a custom error code and parameters for a challenge for a second authentication factor:
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error" : "2fa_required",
"error_description" : "Second factor authentication with OTP required",
"2fa_state" : "wooC3Be2tahmie8ua8chuT0Aizaathu8",
"expires_in" : 120
}
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 repo | https://bitbucket.org/connect2id/server-sdk |
---|
The handler conveys an error by throwing a
GeneralException.
The exception message is logged internally and not
conveyed to the client. The
ErrorObject
determines the HTTP status code, the error
code and any other additional or
custom parameters for the token error response.
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.