Password grant handler
1. Introduction
The OAuth 2.0 framework provides a number of methods (called grants) for clients to obtain tokens from an authorisation server, and one of them is the resource owner password credentials grant.
With this grant the client provides a UI for the end-user to enter their username and password, and submits them to the Connect2id server for verification. The server can also optionally 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 server returns an access token and an optional refresh token, with an appropriate scope and lifetime.
The password grant has better security properties than storing the user credentials in the client. If the client device gets compromised or stolen, the username and password will not be exposed. The tokens can be simply revoked.
Beware: The password grant must be used only with trusted client applications, 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.
Entering user credentials in a third-party client app must never be permitted, because there is risk of credential compromise.
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: server.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 obtained 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 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?
-
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).
-
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, which has 2 jobs 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 registration parameters as decision inputs. The grant handler can also set other token properties, such expiration, encoding, optional refresh token, etc.
-
If the handler flagged the password grant as valid, the Connect2id server creates the appropriate token(s), persists the authorisation (if it’s long-lived and has a refresh token), and returns the token response back to the client.
The Connect2id server enables plugin of password grant handlers via a flexible Java Service Provider Interface (SPI).
This is the interface name:
com.nimbusds.openid.connect.provider.spi.grants.PasswordGrantHandler
To check out the server SDK:
Git repo | https://bitbucket.org/connect2id/server-sdk |
---|---|
Java docs | http://www.javadoc.io/doc/com.nimbusds/c2id-server-sdk |
Maven | Available packages / versions |
Steps:
-
Implement the PasswordGrantHandler interface as described above.
-
Package the SPI implementation (with any other classes) into a JAR with a manifest file called
META-INF/services/com.nimbusds.openid.connect.provider.spi.grants.PasswordGrantHandler
(the name of the interface). Inside it on a single include include the full class name of its implementation, e.g.com.example.MyPasswordGrantHandler
-
Include the password grant handler JAR in the
/libs
folder of the Connect2id server along with with its JAR dependencies (if you have such). -
Restart the Connect2id server.
Important:
- Make sure you use an server SDK version that is compatible with your
Connect2id server. To do that just note the version number of the
WEB-INF/lib/c2id-server-sdk-x.x.jar
distributed with the Connect2id server WAR package. - The Connect2id server can load multiple grant handlers at startup, but only one may be enabled at any one time.
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 remote web service.
Benefits of using this 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 interface for the web service 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. It’s Git repo is at
https://bitbucket.org/connect2id/password-grant-web-api
There is also an example web service implementation available, which uses an LDAP directory to verify the username / password credentials and some simple logic to determine the allowed scope values:
https://bitbucket.org/connect2id/password-grant-web-api-example
3.1.1 Configuration
To set up your web handler for processing password grants you need to provide the Connect2id server with the following configuration details:
-
The URL of your web service for checking the username / password and determining the authorisation scope on success.
-
A long-lived bearer access token for the web service.
-
HTTP connect and read timeouts.
The configuration is stored in WEB-INF/passwordGrantHandlerWebAPI.properties
.
Example configuration:
op.grantHandler.password.webAPI.enable = true
op.grantHandler.password.webAPI.url = https://c2id.com/password-grant-handler
op.grantHandler.password.webAPI.apiAccessToken = ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
op.grantHandler.password.webAPI.connectTimeout = 250
op.grantHandler.password.webAPI.readTimeout = 500
Any configuration file property can be overridden by setting a system-wide
property with the same key, e.g. by using the optional -D
argument at JVM
startup:
-Dop.grantHandler.password.webAPI.enable=false
3.1.2 Web interface
For each access token request with a password grant the Connect2id server will
first check whether the client application has valid client_id
and
client_secret
credentials and 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 steps:
-
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) and the registration 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 Will be set to
application/json
.
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.
-
client {object} JSON object containing the
client_id
and registered metadata for the client. A boolean confidential parameter is also included, to indicate whether the client is confidential or public.
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.
-
[ 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, overriding
the default configuration:
- [ 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).
-
[ 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, overriding the
default configuration:
-
[ 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.
-
[ issue = false ] {true|false} Enables / disables ID token issue.
Defaults to
-
[ 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. Iffalse
no access token will be issued. Defaults tofalse
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.
-
[ issue = true ] {true|false} Enables / disable refresh token
issue. Applies only to long-lived (persisted) authorisations. If
- [ 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 the authorised OpenID Connect UserInfo and other claims, omitted if none.
- [ claims_locales ] {string array} Optional array of the claims locales, omitted if not specified.
-
[ 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. -
[ 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.
- 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 000123
, 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
{
"username" : "bob",
"password" : "secret",
"scope" : [ "openid", "email", "profile" ],
"client" : { "client_id" : "000123",
"client_name" : "My Test App",
"confidential" : true,
"grant_types" : [ "password" ],
"response_types" : [],
"token_endpoint_auth_method" : "client_secret_basic",
"application_type" : "web" }
}
Example response indicating 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" : "67890",
"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. Support
Our Connect2id support team is available if you need help with configuring the web-based password grant handler or implementing your own.