AuthorizationServer
Introduction
The OAuth 2.0 authorization server implements the authorization endpoint and the token endpoint of the OAuth 2. 0 authorization framework. In addition to plain OAuth2, it also supports OpenID Connect (Core).
In accordance with the specification, the AuthorizationServer issues tokens based on the client requesting it, the requested scopes and the end user's consent to this request. Before clients can request tokens, the client as well as the scopes it would like to request must be registered at the AuthorizationServer. Failure to do so will result in an error.
The AuthState validates incoming authorization and token requests and selects the transition that best matches the validation result. It is typically the first state in the processing chain and should be configured to be called on authenticate as well as stepup to act according to the specification.
Results
The invalid-client and invalid-redirect-uri transitions signal that the client is not correctly registered at the authorization server, whereas an invalid-authorization-request signals that the authorization request was not valid. In these cases, a transition to the AuthError AuthState itself is recommended to make sure the authorization server acts as required by the OAuth 2.0 specification and sends the correct error responses.
A valid OAuth 2.0 authorization request or OpenID Connect authentication request is signaled with valid-authorization-request and valid-authorization-request-authentication-required, respectively. In both cases, the AuthState is registered as a finisher, such that the AuthEngine will hand back control to the AuthorizationServer AuthState once AUTH_DONE or AUTH_ERROR is reached to generate a meaningful authorization response. For example., from valid-authorization-request-authentication-required and valid-authorization-request, the state engine can be configured as usual. Note that the OAuth 2.0 specification requires user authentication to take place in that case. After successfully authenticating the user, a transition to the AuthDoneAuthState is recommended to hand back control to the authorization server, which then issues a successful authorization response. When the user cannot be successfully authenticated, a transition to the AuthError AuthState is recommended to hand back control to the authorization server, which then issues an authorization error response.
When receiving a token request, the AuthState validates it and signals the result using the valid-token-request or invalid-token-request transition. In both cases, the AuthState is registered as a finisher, such that the AuthEngine will hand back control to the AuthorizationServer AuthState once AUTH_DONE or AUTH_ERROR is reached to generate a meaningful token response. Note that the client authentication as required by the specification already took place at this point.
The server-error result signals an internal server error. A transition to the AuthState itself is recommended to make sure the authorization server acts as required by the OAuth 2.0 specification and sends the correct error responses.
Client and scope registration and policies
Before clients can request access tokens, the clients need to be registered and configured at the AuthorizationServer. Besides the client ID, this also includes a client name, redirect URIs and policies applying for that client (e.g., whether a client may request certain scopes). Based on the client configuration, the AuthorizationServer authenticates clients and checks whether the policy allows the request sent by them. The following policies can be configured for in
- Scopes: Defines what scopes a client is allowed to request.
- Redirect URIs: Redirection URI values used by the client.
- Response types: OAuth2 response type values the client is restricted to use.
- Grant types: OAuth2 grant types the client is restricted to use.
- Authentication required policy: Defines whether a request by this client requires forced (re)-authentication of the end user.
These policies are enforced by the AuthorizationServer. If a request violates a policy, this is signaled by an authorization error response, a token error response or an error displayed to the end user, and the result is set to invalid-authorization-request, invalid-token-request or invalid-redirect-uri respectively.
As with the clients, scopes must be registered at the AuthorizationServer before they can be requested. Besides the scope name, this also includes policies applying to a scope. The following policies can be configured for in
- Implicit flow policy: Defines whether the scope can be requested via the implicit flow and how the end user's consent can be established.
- Authorization code flow policy: Defines whether the scope can be requested via the authorization code flow and how the end user's consent can be established.
- Client credentials flow policy: Defines whether the scope can be requested via the client credentials flow.
- Refresh token request policy: Defines whether the scope can be requested in combination with requesting a refresh token to be issued and how the end user's consent can be established.
- Authentication required policy: Defines whether requesting the scope requires forced (re)-authentication.
These policies are enforced by the AuthorizationServer. If multiple policies apply to a request, the most restrictive policy is applied. If a request violates a policy, this is signaled by an authorization error response or a token error response and the result is set to invalid-authorization-request or invalid-token-request respectively.
Scope processing
This chapter contains a brief clarification of scope processing in the AuthorizationServer. In general, the behavior follows RFC 6749. Some underspecified points in the RFC are made explicit here:
- Authorization Request processing. The authorization request scope parameter is validated against the set of scopes the corresponding client may request (according to the client metadata) and against the policies for the given response type. If no scope request parameter is given, this is accepted and evaluated as the empty scope.
- Token Request processing. Independent of the grant type, the token request scope parameter is validated against the set of scopes the corresponding client may request. This happens even in cases where there should not be a scope request parameter (see RFC 6749, section 4.1.3 for the details). Whether the scope request parameter is taken into account when issuing the tokens differs between the grant types:
Special scopes
The authorization server implements a few scopes that are treated specially at the AuthorizationServer when requested. Unlike regular scopes, these scopes are understood and interpreted by the AuthorizationServer itself, and measures are taken to implement the behavior specified for these scopes.
The following scope values defined by the OpenID Connect specification are understood by the AuthorizationServer:
- openid
- profile
- address
- phone
Furthermore, the scope value offline_access requests that a Refresh Token will be issued if allowed according to the specification. See the OpenID Connect specification for details.
As with the regular scopes, these special scopes must be registered before they can be used.
Supported flows
The OAuth 2.0 authorization server plug-in supports the implicit flow, hybrid flow, authorization code flow and client credentials flow.
Implicit flow
In the implicit flow, access tokens and ID tokens are issued by the authorization endpoint.
Here is an example of an HTTP authentication request for the implicit flow:
GET /authorize?
response_type=id_token%20token
&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&scope=openid%20profile
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj HTTP/1.1
Host: server.example.com
Authorization code flow
In the authorization code flow, an authorization code must be requested from the authorization endpoint. Here is an example of an HTTP authentication request for the authorization code flow:
GET /authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
Host: server.example.com
Using the acquired authorization code, tokens can be requested from the token endpoint using a token request. The token request must contain the authorization code issued by the authorization endpoint. This AuthState implements both endpoints.
When the client requests an access token via a token request, the client must authenticate itself. There are two possible ways:
- The client includes its credentials in the request body, via the client_id and client_secret parameters.
- The client submits its credentials via the Authorization header.
If the client is configured as public, it must not necessarily have a secret. For clients without secret, no client authentication is required in the request. This follows the following excerpt of the OAuth specification*:
"The authorization server MAY establish a client authentication method with public clients. However, the authorization server MUST NOT rely on public client authentication for the purpose of identifying the client."
However, if the client does have a secret, authentication is required, as specified in the following section of the OAuth specification**:
"Confidential clients or other clients issued client credentials MUST authenticate with the authorization server as described in Section 2.3 when making requests to the token endpoint."
) https://tools.ietf.org/html/rfc6749#section-2.3
) https://tools.ietf.org/html/rfc6749#section-3.2.1
Here is an example of an HTTP token request containing the credentials in the request body:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&client_id=s6BhdRkqt3
&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
Alternatively, the HTTP basic authentication scheme can be used.
Here is an example of an HTTP token request using the basic authentication scheme:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
In the previous example, czZCaGRSa3F0MzpnWDFmQmF0M2JW represents the base64-encoded client ID and client secret, separated by a colon ("<client id>:<client secret>
").
Note that in the authorization code flow, the Proof Key for Code Exchange or "PKCE" extension specified in RFC 7636 is fully supported. If PKCE is used, requests from the client contain the additional parameters (code_challenge, code_challenge_method, code_verifier) described in RFC 7636. For more information, see https://tools.ietf.org/html/rfc7636
.
The level of enforcement of PKCE can be defined on a per client basis. By default and for backwards compatibility reasons, PKCE will be allowed for the client. As soon as a client uses a code challenge, the challenge will automatically be verified at the token endpoint. However, the client is not forced to send PKCE information. For security reasons, it is recommended enforcing the use of PKCE using the S256 code challenge method if the client supports it. Use the attribute pkceMode in the table Configuration of the AuthorizationServer below for a client using a local metadata source. See the nevisMeta reference guide for information regarding how to configure PKCE when nevisMeta is used as metadata source.
Client credentials flow
In the client credentials flow, a client can request an access token using only its client credentials. In that case, the client makes an HTTP request directly to the token endpoint. Example:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
If the authorization server could authenticate the client, an access token response containing the access token is issued.
Token format
The AuthorizationServer returns tokens in JSON format. The format of the response differs depending on whether OpenID is supported. If yes, the response contains an additional token (in the attribute id_token).
- The web page OpenID core specification](https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse)" decribes the format of the response containing the tokens if OpenID is supported.
- The web page RFC-6749:
https://tools.ietf.org/html/rfc6749#section-4.1.4
decribes the format in case OpenID is not supported.
The next example shows a response (with a shortened token value in the attribute id_token):
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "SlAV32hkKG",
"token_type": "Bearer",
"refresh_token": "8xLOxBtZp8",
"expires_in": 3600,
"id_token": "eyJhbGciOiJ"
}
The response generated by the AuthorizationServer includes the attribute access_token.The value of the access_token attribute is a JSON web encryption https://tools.ietf.org/html/rfc7515
object.
The access token payload contains the following claims:
- sub: Subject, string. Set to the user ID of the authenticated user (or the client ID, in case of the client credentials flow).
- scope: Scopes requested. The scope values are separated by whitespace.
- client_id: Client who sent the request.
- iss: The issuer of the ID Token and Access Token. This field is only written when OpenID Connect is enabled.
- openid.claims.requested: If applicable, defines the OpenID Connect claims requested from the Userinfo endpoint as a JSON string.
- nbf: Date before which the token should not be accepted. Format: seconds since epoch.
- exp: Date after which the token should not be accepted anymore. Format: seconds since epoch.
- iat: Date when token was issued. Format: Seconds since epoch.
- ch.adnovum.nevisidm.profileId: This claim is automatically set to the value "${sess:ch.adnovum.nevisidm.profileId}", contained when issuing the original authorization response. Given the same key material, it is compatible with the AccessTokenConsumerAuthState.
SignatureTo sign the claims, the AuthorizationServer uses the private key that is specified in the attributes keyobjectref and keystoreref.
The signature algorithm is RS256: RSASSA-PKCS-v1_5, using the SHA-256 hash algorithm.
The table below Configuration of the AuthorizationServer], the AuthorizationServer uses the public key specified in the attributes keyobjectref and keystoreref.
The encryption algorithm is RSA_OAEP_256: RSAES, using Optimal Asymmetric Encryption Padding (OAEP) (RFC 3447), with the SHA-256 hash function and the MGF1 with SHA-256 mask generation function. The encryption method is A256GCM: AES in Galois/Counter Mode (GCM) (NIST.800-38D) using a 256 bit key.
The table below Configuration of the AuthorizationServer].
ExampleIn the next example, the AuthorizationServer generates an access token with a payload containing the following claims:
Access token payload example
{
"aud":"audience",
"sub":"the subject",
"nbf":1562741304,
"scope":"scope1",
"iss":"an issuer",
"exp":1864594800,
"iat":1562741304,
"client_id":"the client id"
}
This payload is signed according to the(https://tools.ietf.org/html/rfc7516), with the public key that is also specified in the AuthorizationServer configuration. Subsequently, the encrypted JWS object (a JWE object) is transformed into a URL-safe string using compact serialization. The result is a token shown as a string in the next code block - note the different elements, separated by period characters:
Access token example
eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.MNhMZxhNJZ08IIwXitroji5-GFbH3qG0PokojvbZ1yM6-7Jc7mJjqxMzwEeYefvjpNmuQP5SKw3HKG52qaGGMaKQrYZA0U7pt9-r3Ees5DFfJq5ODXLbb1zL-eK9axpUUMlqKPI2bWaGfbnhhw5M4sOqmYCb809cnIOhBWYObBg.pgHZBBdr2pqq-VHQ.58bC5A7VB0Y1iwInmVfTKIOioZEXV2TKX2fC9DYQNMunhw1OK-oDkQhgIDNSEAdE9UFO_1K1G7eT6SfFjRmJpry0_bNWnvwwI3oBKMBGaJjnyR8-XVa7czNIrzlXu7JcuVmzBVxsELrYApzlnYr6dWxSrVBlqUAWOG6DH1LLLXkYT5oGTi_GueetSze0RTwNqZU3s6Oo2riMh2y-DZkF43pWZJcb0Dj49EvSpx_wWYLG4HZPh7Tnu2u8Gr549vBA_yi94DOBWJQl77BbmgFmuQKvBP9ftW9lWbtDLgy4A5h6uFQPVnkb4uIOoNQ_QX0rjYek620nEldpr7wr4rsGo4qDFxty8LXiALEmizT9nggLOo7yzwdEvR9rZoClGanHzJQIhdeFuxbVZ_MBRTBBOl0jema5yW2-wfOEzqKv4B4vAfTiTYkS7BcWOTjCJD5On5fuztYpNopARK_QhfxCgiY9BuReH6o_IaUbUVdIQjOujOgQ2gdpHLEsRWJtI65pYf8aGPmuTuHCaz7j.4TuBTuQOiklfmHOyKI9hUA
Claims extracting code - exampleThe following Java code example uses the(https://connect2id.com/products/nimbus-jose-jwt/examples/jws-with-rsa-signature).
Claims extracting code - example
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.security.interfaces.RSAPrivateKey;
import java.text.ParseException;
import java.util.Enumeration;
import java.util.Objects;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class AccessTokenClaimsExtractor {
private final RSADecrypter decrypter;
public static void main(String[] args) throws Exception {
// Test method, it is assumed that the token was signed using the public key of a certificate
// whose asociated private is stored in a PKCS12 keystore located in /tmp/keystore.p12.
String token = "eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.mbzOp9wMydLJ3m6SNX0YaqTzRRom7LRBEBT0cDuNVtuHS8RGy3ScvBWvN1ct2OLYZQd1cDUII4jTz7b0AR3lEnGKqlNyh50g0PRskZD5MEVh98Vs657kBv3kJxOQdnxBPmZAh6VCpBDkfxu6qiOogUcag4rHKdqqdog1Ugf6nXiffiIGBR2SkToB4KRkSmJBAX3Wubm38k2hyo2iXeIU7tg3bE3J2GrunhmoXqkEWcZ4iZktmOyhhPlyx990_w7_n05QoQ8t5Dmemmg16XouR0gb1oJgkOYxYIlBg5BYtACiJWOq2puL2VhQs1hf2tZeMO8cvMqMLyfi4rnb7PfGxQ.220heA9uswUGUWr2.e16gdo679Njexk-b2TEslkG8nsq_zv_Oq2Vl7luZfw5tDf_8NG1WrNtpNnjj0AKKG74NPUQVpJxFm6rZ0FS8ITAYjK9UgY3pOL8YiGYCHyj9b7l5B2xldmuLAT_ngw86FkrqT7ZH8iZ5_lamOA5vs_ycjkgj9bd5T2uosGt2JtS9jV9e1GF2M7inj9Mi90e2pYwp3mYUlc0GDi1qYvBU1bfVUNJe4fZThY0qvn84wjg4vCSahq966qzJGd5_Ez1rLThTOBqrnRNCJjBi_N-rwosilcxhNHhnyN650FIxCh3xUJbuqdZ0OlddxqeFrc1P9aFbZbvH8slzkCLndrDhJZHWuZ_B_K6Ra14iQQcySTdLSw5zIV7yNGqDg0vAZ3k9Pn2B_50ENXgR2Yrt4cgfyg-woqHQrzo-iUAlfLIub954pwt0POO5rYFqq7nBMFn5nCsGPid63wrW1yaR4yWLL0BxjXYaENSD-kC6zG2QDrzgQje713k_zfdwiGBTfDc_mSb5PyH-afp64Ve3xHUsln5uYDYwwhivPkiCzLqDyLhGtHJAEz2xS2MtLc0ta2vWvHS2T2PYJAiiAVGzAM4u-_fXoYG_ZiCMJ06jKcgpR5T1-FZgQVT8kj7MW9bNEmoYOl2QBNfkRYCgIeK1UlS4l-9gzpBYOZ8LPM7ONNuxpbnRoj8lkWRKcsDo0BJ1ZSKFDc3ssMkeRdp36lFsA1nlQLGxrUSlDRqRJrHAyhMiM8C-K4jKImMO.HW2mVej8bYmZ5gTJmGD9iQ";
RSAPrivateKey rsaPrivateKey = extractPrivateKey("/tmp/keystore.p12",
"password".toCharArray(), "PKCS12");
RSADecrypter decrypter = new RSADecrypter(rsaPrivateKey);
AccessTokenClaimsExtractor extractor = new AccessTokenClaimsExtractor(decrypter);
JWTClaimsSet claimsSet = extractor.extractClaims(token);
// Handle the claims, here the code just converts the claims to a JSON string and prints them.
String json = claimsSet.toJSONObject().toString();
System.out.println("Claims as JSON:");
System.out.println(json);
}
public AccessTokenClaimsExtractor(RSADecrypter decrypter) {
this.decrypter = Objects.requireNonNull(decrypter);
}
public JWTClaimsSet extractClaims(String token) throws ParseException, JOSEException {
JWEObject jweObject = JWEObject.parse(token);
jweObject.decrypt(decrypter);
Payload payload = jweObject.getPayload();
SignedJWT signedJwt = payload.toSignedJWT();
return signedJwt.getJWTClaimsSet();
}
private static RSAPrivateKey extractPrivateKey(String keyStorePath, char[] password,
String keyStoreType) throws IOException, GeneralSecurityException {
FileInputStream is = new FileInputStream(keyStorePath);
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(is, password);
Enumeration<String> aliases = keyStore.aliases();
if (!aliases.hasMoreElements()) {
throw new IllegalStateException("No aliases defined in keystore");
}
// The example assumes that the keystore contains only one alias
String alias = aliases.nextElement();
// The example assumes that both the keystore and the keys are protected using the same
// password
Key key = keyStore.getKey(alias, password);
// Currently only RSA keys are supported by the AuthorizationServer
if (!(key instanceof RSAPrivateKey)) {
throw new IllegalStateException("The key in the keystore is not an RSAPrivateKey");
}
return ((RSAPrivateKey)key);
}
}
If OpenID Connect is supported, the AuthorizationServer will generate an ID token in addition to the access token. This ID token is provided as the value of the id_token attribute of the response. The ID token is a signed JSON web token JWT object containing the following default claims:
- iss: Issuer identifier, string. See the property openid.issuerId in the. This table describes the AuthorizationServer AuthState.
- sub: Subject, string. Set to the user ID of the authenticated end user.
- aud: Audience, array of strings. Set to the client ID.
- exp: Expiration time, number (Unix timestamp in seconds).
- iat: Issued at, number (Unix timestamp in seconds).
Furthermore, the ID token contains the claims that are requested to be included (via scope or claims request), according to the OpenID Connect specification.
Limitations
The AuthorizationServer currently ignores the acrclaims. This implies that no acr will be included in the OpenID ID token, even in the case where the acr is requested as an essential claim of the Authorization Request.
SignatureTo sign the claims, the AuthorizationServer uses the private key that is specified in the attributes keyobjectref and keystoreref.
The signature algorithm is RS256: RSASSA-PKCS-v1_5, using the SHA-256 hash algorithm.
The contains a description of the configuration attributes keyobjectref and keystoreref. Serialization
The token is returned as a URL-safe string using JWS compact serialization as described in https://tools.ietf.org/html/rfc7515#section-7.1
.
ExampleIn the next example, the AuthorizationServer generates an ID token with a payload containing the following claims:
ID token payload example
{
"sub":"authenticatedUser",
"aud":"responseTypeCodeTokenIdTokenClientId",
"iss":"https:\/\/siven.ch\/oauth2",
"exp":1563439848,
"iat":1563439248,
"nonce":"xyz"
}
This payload is signed according to the https://tools.ietf.org/html/rfc7515
, using the private key defined in the AuthorizationServer configuration. The JWS object is then transformed into a URL-safe string using compact serialization. The result is a token shown as a string in the next code block - note the different elements, separated by period characters:
ID token example
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhdXRoZW50aWNhdGVkVXNlciIsImF1ZCI6InJlc3BvbnNlVHlwZUNvZGVUb2tlbklkVG9rZW5DbGllbnRJZCIsImlzcyI6Imh0dHBzOlwvXC9zaXZlbi5jaFwvb2F1dGgyIiwiZXhwIjoxNTYzNDQwNTg2LCJpYXQiOjE1NjM0Mzk5ODYsIm5vbmNlIjoieHl6In0.Uzvl7rzjFww7rwXp1KAKMf8IqxKuoPQFo9SkfcFE6us9gIAn47jU94fO6p-TVISE_4KCkBtqFw52Ph26ztS73y8pHd6VebjK7I4uwu7EcwCVlugCupoRysba-DFKWGyl1A3ZwUSreOxJ4ZYj90XR1SWE1jrvK3J9hNHPcITlAAs
Claims extracting code - exampleThe following Java code example uses the(https://connect2id.com/products/nimbus-jose-jwt/examples/jws-with-rsa-signature).
Claims extracting code - example
import java.text.ParseException;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class IDTokenClaimsExtractor {
public static void main(String[] args) throws ParseException {
String token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhdXRoZW50aWNhdGVkVXNlciIsImF1ZCI6InJlc3BvbnNlVHlwZUNvZGVUb2tlbklkVG9rZW5DbGllbnRJZCIsImlzcyI6Imh0dHBzOlwvXC9zaXZlbi5jaFwvb2F1dGgyIiwiZXhwIjoxNTYzNDQwNTg2LCJpYXQiOjE1NjM0Mzk5ODYsIm5vbmNlIjoieHl6In0.Uzvl7rzjFww7rwXp1KAKMf8IqxKuoPQFo9SkfcFE6us9gIAn47jU94fO6p-TVISE_4KCkBtqFw52Ph26ztS73y8pHd6VebjK7I4uwu7EcwCVlugCupoRysba-DFKWGyl1A3ZwUSreOxJ4ZYj90XR1SWE1jrvK3J9hNHPcITlAAs";
JWT idTokenDeserialized = SignedJWT.parse(token);
// Handle the claims, here the code just converts the claims to a JSON string and prints them.
IDTokenClaimsExtractor extractor = new IDTokenClaimsExtractor();
JWTClaimsSet claimsSet = extractor.extract(token);
String json = claimsSet.toJSONObject().toString();
System.out.println("Claims as JSON:");
System.out.println(json);
}
private JWTClaimsSet extract(String token) throws ParseException {
SignedJWT signedJwt = SignedJWT.parse(token);
return signedJwt.getJWTClaimsSet();
}
}
OAuth2 and OpenID Connect setups with nevisMeta
The OAuth 2.0 authorization server of nevisAuth offers a connector to nevisMeta to support the management of OAuth2 and OpenID Connect setups. With nevisMeta, it is possible to manage entities such as clients and resources in a central location via a comfortable GUI. Using nevisMeta, clients and scopes are not required to be configured within the AuthState but can be managed within nevisMeta. The connector can be activated by setting the dataSource property of the OAuth 2.0 authorization server configuration to nevismeta. It is necessary to provide the endpoint address of a nevisMeta instance as well as a specific SecToken. The parameters involved are described in.
It is important to know that the nevisMeta connector in nevisAuth uses a time-out-based caching mechanism for querying data from nevisMeta. During the startup, the nevisMeta connector fetches all data (clients, resources, etc.) stored in the connected nevisMeta instance. There is no further interaction between nevisAuth and nevisMeta until a configurable time-out is reached. Once this time-out is reached, the connector fetches the whole dataset again. The properties related to the caching as well as further connection-related properties are described in.
Configuration of the AuthorizationServer
For all flows, the prior registration of the supported scopes, the client and its redirect URIs at the AuthorizationServer is mandatory. An architecture has been implemented to support multiple sources for client and scope configuration data.
Scope policies
Scope configuration allows to set policies for the approval process of in
- Implicit flow policy (CONSENT_POLICY): Configures how consent for the scope is established when requested via the implicit flow.
- Authorization code flow policy (CONSENT_POLICY): Configures how consent for the scope is established when requested via the authorization code flow.
- Client credentials flow policy (true/false): Defines whether the scope can be requested via the client credentials flow.
- Refresh token request policy (CONSENT_POLICY): Defines how consent is established when requested in combination with a refresh token.
- Authentication required policy (true/false): Configures whether a request for that scope should lead to forced (re-)authentication.
CONSENT_POLICY can take one of the following values (ordered according to their strictness):
- Disallowed: This scope cannot be granted.
- Consent required: End-user consent is always required.
- Consent persisted (default): End-user consent is required once and then persisted in the session.
- No consent required: End-user consent is not required.
For the consent policies consent required, consent persisted and no consent required, compliance is enforced by the ConsentState (see chapter 5.9.1.2 "ConsentState"). If more than one policy applies to a request, the strictest policy is enforced.
Topic | Description |
---|---|
Class | ch.nevis.esauth.auth.states.oauth2.AuthorizationServer |
Logging | OAuth2 |
Auditing | none |
Marker | "OAUTH2:token" and "OpenIDConnect:federation" |
Properties | accessTokenLifetime (seconds, 3600)Defines how long an access token issued by the authorization server should be valid per default (can be overwritten by client policies).Note that the calculation of the expiration time is based on the issuing time of the authorization code, not on the time the access token is returned in the AuthorizationServer's response. |
refreshTokenLifetime (seconds, 1 year)How long a refresh token issued by the authorization server should be valid per default (can be overwritten by client policies). | |
authCodeLifetime (seconds, 600)How long an authorization code issued by the authorization server should be valid. | |
keystoreref (string, "DefaultKeyStore")keyobjectref (string, "DefaultSigner")This property configures the key and certificate to use when issuing tokens. The access token is encrypted using the configured public key. The ID token is signed using the configured private key. Currently, only RSA keys are supported.If the nevisAuth instances are configured in a load balancing or load balancing configuration, the keystoreref of all the instances should reference the same keys. | |
keyID (string, - )This property configures the kid JOSE header value of the issued access and ID tokens. This value allows the authorization server to explicitly signal a change of key material to recipients. The meaning of the kid header is slightly different for(https://tools.ietf.org/html/rfc7516#section-4.1.6) tokens. | |
dataSource (string, "local")Determines what data source should be used to configure OAuth 2.0 clients and scopes. With dataSource=local, client and scope configuration data is supplied via configuration properties. With dataSource=nevismeta, client and scope configuration data is supplied via nevisMeta. | |
propagationScope (string, "session")Define propagation scope to store information for following AuthStates. | |
Properties(only effective if dataSource= local) | Basic client configuration:All clients must be registered and configured before sending authorization and token requests. |
client.[clientId] (string, - )Registers a client. | |
client.[clientId].secret (string, - )Client secret for client with ID [clientId]. Replace [clientId] by the client identifier.This attribute is required if the client type is "confidential". If the client is "public", it is optional. The client type is set in the property client.[clientId].type. | |
Client metadata configuration:For every client, client metadata needs to be configured. The following values are internally mapped to a client metadata representation described in `http://tools.ietf.org/html/draft-ietf-oauth-dyn-reg-21#section-2. The client metadata is then propagated as described in the topic Output in this table. | |
client.[clientId].scope (string, - )Space-separated list of scopes the client is allowed to request. These scopes must be registered at the AuthorizationServer.Client metadata mapping: scope field of the client metadata. | |
client.[clientId].clientName (string, clientId )Name of the client. Note that this is not the same as the clientId. The clientId is a unique identifier, often random, whereas the client name is a human readable name.Client metadata mapping: client_name field of the client metadata. | |
client.[clientId].clientName.[langTag] (string, - )Language-specific name of the client.Client metadata mapping: client_name#langTag field of the client metadata. | |
client.[clientId].redirectUrl (string: URIs -)Space-separated list of registered redirection URIs for the client [clientId]. The redirection URIs of the client must be registered to be accepted as valid redirection URIs by the AuthorizationServer.Client metadata mapping: redirect_uris field of the client metadata. | |
client.[clientId].responseTypes (string, "code")Space-separated list of the OAuth2 response types the client will restrict itself to using. Valid values are code, token, and id_token.Client metadata mapping: response_types field of the client metadata. | |
client.[clientId].grantTypes (string, "authorization_code")Space-separated list of the OAuth2 grant types the client will restrict itself to using. Valid values are authorization_code, implicit, refresh_token, and client_credentials.Client metadata mapping: grant_types field of the client metadata. | |
client.[clientId].authenticationRequiredPolicy (boolean, "false")Defines whether a request by this client requires forced (re)-authentication of the end user (result valid-authorization-request-authentication-required).Client metadata mapping: client_force_authentication field of the client metadata. | |
client.[clientId].accessTokenTtl (accessTokenLifetime property)Defines the lifetime of the access token when requested by this client.Client metadata mapping: access_token_ttl field of the client metadata. | |
client.[clientId].idTokenTtl (seconds, openid.idTokenLifetime property)Defines the lifetime of the ID token when requested by this client.Client metadata mapping: id_token_ttl field of the client metadata. | |
client.[clientId].refreshTokenTtl (seconds, refreshTokenLifetime property)Defines the lifetime of the refresh token when requested by this client.Client metadata mapping: refresh_token_ttl field of the client metadata. | |
client.[clientId].logoUri (string, - )Default logo of the client.Client metadata mapping: logo_uri field of the client metadata. | |
client.[clientId].logoUri.[langTag] (string, - )Language specific logo of the client.Client metadata mapping: logo_uri#langTag field of the client metadata. | |
client.[clientId].type (string, "confidential")The type of client.The AuthorizationServer supports two types of clients: "confidential" (default) and "public". | |
client.[clientId].pkceMode (string, "allowed")Whether the client requires the use of PKCE in the authorization flow. The following types of PKCE modes are supported:"allowed" (default): If the client sends PKCE information in the form of a code challenge in the authorization request, the code challenge will be validated. If the code challenge is not valid, the authorization will fail. But if no code challenge is included in the authorization request, the authorization will not fail. "required": The client must send valid PKCE information. If no code challenge is included in the authorization request, the authorization will fail. * "s256-required": The client must send valid PKCE information using the S256 code challenge method. The authorization will fail if no code challenge is included in the authorization request, or if the code challenge does not use the S256 code challenge method. If the client supports the s256 code challenge method, then "s256-required" is the recommended value.For more information, see PKCE: https://tools.ietf.org/html/rfc7636#section-4.4.1 . | |
Scope configuration:All scopes must be registered before they can be requested by clients. | |
scope.[scopeName] (string, -)Registers a scope. | |
scope.[scopeName].implicitFlowPolicy (string:, "CONSENT_PERSISTED")Configures how consent for the scope is established when requested via the implicit flow. | |
scope.[scopeName].clientCredentialsFlowPolicy (boolean, "true")Configures whether the scope can be requested via the client credentials flow. | |
scope.[scopeName].authorizationCodeFlowPolicy (string:, "CONSENT_PERSISTED")Configures how consent for the scope is established when requested via the authorization code flow. | |
scope.[scopeName].refreshTokenRequestPolicy (string:, "CONSENT_PERSISTED")Configures how consent for the scope is established when requested in combination with a refresh token. | |
scope.[scopeName].authenticationRequiredPolicy (boolean, "false")Configures whether a request of this scope should lead to forced (re)-authentication of the end user (signaled by the valid-authorization-request-authentication-required result). | |
Properties(only effective if dataSource= nevisMeta) | nevismeta.location (string, -)The entity endpoint of a nevisMeta instance (for example: https:///> entities ) |
nevismeta.maxAge (int, 600)Caching of responses from a nevisMeta instance. After this time (in seconds), a response is considered outdated and attempts are made to update it. | |
nevismeta.discardInterval (int, 60)Caching of responses from a nevisMeta instance. If a query/update failed, do not try to update again until this interval (in seconds) has passed. This is to avoid continuous requests in case of connection failures. A default retry interval of one minute is defined. | |
nevismeta.http.connection.timeout (int, 10)The time-out (in seconds) for establishing connections to a nevisMeta instance. | |
nevismeta.http.socket.timeout (int, 30)The time-out (in seconds) for reading data from a nevisMeta instance. | |
Properties(OpenID Connect) | openid.support (boolean, "false")If set to true, OpenID Connect is supported. |
openid.issuerId (string, -)The iss claim value of the ID token and the access token. Identifies the issuer of the ID token and the access token. Must be set to a case-sensitive URL using the https scheme. | |
openid.idTokenLifetime (seconds, 600)How long the ID token should be valid per default (can be overwritten by client policies). At most a few minutes are recommended. | |
openid.claimsParameterSupported (boolean, "true")If this property is set to "false", the system will ignore the claims requested via the claims parameter in the OpenID Authorization Request. Only the claims requested through the scope values will be considered. If set to "true", the system will manage both the claims requested via the scope values and the claims parameter. For more details, see `http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter. The claims_parameter_supported result mentioned in this specification is equivalent to the property openid.claimsParameterSupported described here. | |
openid.claim.[claimName] (string, -)Defines the value of the claim to be integrated in the ID token when requested.You can use JSON syntax to specify arrays and JSON values.Examples:Generates a single value ("value1,value2") for the claim "myattr": <property name="openid.claim.myattr" value="value1,value2"/> Generates a single value ("null") for the claim "myattr": <property name="openid.claim.myattr" value="null"/> Generates an array with three values (1, "value2" and null) for the claim "myattr": <property name="openid.claim.myattr" value=1,"value2",null/> Generates a JSON object consisting of one attribute named "attr1" with a value ("value1") for the claim "myattr": <property name="openid.claim.myattr" value="{"attr1,":"value1"}"/> Assuming that notevalue contains a value null, generates an array with one value (null) for the claim "myattr": <property name="openid.claim.myattr" value="${notes:notevalue}"/> Note that requesting claims via scope does not necessarily lead to their inclusion in the ID token. This is according to the specification and documented in https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims. | |
openid.userInfoEndpointUri (string, -)If this property is set, the userinfo data that comes from the configured endpoint is used to specify the claim values in the ID token. Individual claim values are replaced by the values explicitly configured via openid.claim.[claimName] . The claim values used to generate the ID token are fetched from the configured userinfo endpoint. nevisAuth sends the request on behalf of the subject using a self-issued SecToken. It expects the configured endpoint to return a userinfo response as defined in http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse. This property is required if relying parties use refresh tokens to retrieve fresh ID tokens (i.e., authorization code flow in combination with OpenID Connect support). The following example describes a scenario in which set this property: After a successful OpenID Connect authentication request, the relying party receives an ID token and a refresh token from the Identity Provider. Later - when the ID token expires - the relying party may intend to receive a fresh ID token using the refresh token. This happens outside the context of the subject’s session (the request is directly made by the relying party without the subject’s presence). Therefore, the Identity Provider does not have access to the subject’s up-to-date profile information within the session (as opposed to when the original authentication happened). The Identity Provider thus needs other means to get access to the subject’s profile data. Note that if OpenID Connect is supported and the attribute openid.userInfoEndpointUri is set, hostname verification is enabled by default. The relevant property is openid.userinfo.httpclient.checkHostname (boolean, true). When establishing an HTTPS connection, the client will check the hostname in the HTTPS server certificate against the actual server hostname by default. To disable hostname verification,set the property openid.userinfo.httpclient.checkHostname to "false". See below for more information about the prefix openid.userinfo.httpclient. . | |
openid.userinfo.httpclient.*If the propertyopenid.userInfoEndpointUriis set, configure the HTTP properties for the outgoing HTTP communication by using the prefix "openid.userinfo.httpclient.". Find a list of valid properties in the chapters AuthHttpClient. | |
Properties(OAuth2) | oauth2.claim.[claimName] (string, -)Defines the value of the claim to be integrated in the access token when requested.You can use JSON syntax to specify arrays and JSON values.Examples:Generates a single value ("value1,value2") for the claim "myattr": <property name="oauth2.claim.myattr" value="value1,value2"/> Generates a single value ("null") for the claim "myattr": <property name="oauth2.claim.myattr" value="null"/> Generates an array with three values (1, "value2" and null) for the claim "myattr": <property name="oauth2.claim.myattr" value=1,"value2",null/> Generates a JSON object consisting of one attribute named "attr1" with a value ("value1") for the claim "myattr": <property name="oauth2.claim.myattr" value="{"attr1,":"value1"}"/> Assuming that notevalue contains a value null, generates an array with one value (null) for the claim "myattr": <property name="oauth2.claim.myattr" value="${notes:notevalue}"/> |
Methods | process: Will initiate the OAuth 2.0 protocol. Implements the authorization endpoint as well as the token endpoint. It thus will accept OAuth 2.0 authorization requests as well as OAuth 2.0 token requests. |
finish: Will generate the authorization/token response. | |
handleError: Will generate the authorization/token error response. | |
Input | none |
Transitions | invalid-client: The client sending the request is not registered. |
invalid-redirect-uri: The redirect_uri request parameter value is not registered for the client sending the request. | |
invalid-authorization-request: The authorization request is invalid. | |
valid-authorization-request: Authorization request is valid, end-user authentication expected as the next step. | |
valid-authorization-request-authentication-required: Authorization request is valid, forced end-user authentication expected as the next step. | |
valid-token-request: Token request is valid. Tokens are about to be issued. The user ID has been set to the client_id. | |
invalid-token-request: Token request is invalid. Token error response is about to be issued. | |
server-error: Something went wrong internally. | |
Output | On authorization request: |
Authorization request:[propagationScope]:oauth2.authorization_request.[requestParameter]All authorization request parameters are propagated using the configured propagationScope (see property propagationScope in this table). [requestParameter] is a placeholder for the name of the request parameter. | |
Client configuration:[propagationScope]:oauth2.client.idClient ID of the client that issued the authorization request.[propagationScope]:oauth2.client.metadata.[field]Metadata of the client that issued the authorization request. | |
Scope configuration:[propagationScope]:oauth2.scope.policy.clientCredentialsFlowScope policy parameter as configured by the property dataSource.[propagationScope]:oauth2.scope.policy.authorizationCodeFlowScope policy parameter as configured by the property dataSource.[propagationScope]:oauth2.scope.policy.implicitFlowScope policy parameter as configured by the property dataSource.[propagationScope]:oauth2.scope.policy.refreshTokenRequestScope policy parameter as configured by the property dataSource.[propagationScope]:oauth2.scope.policy.authenticationRequiredScope policy parameter as configured by the property dataSource.[propagationScope]:oauth2.scope.metadata.[field]Scope metadata as configured by the property dataSource. | |
Errors | none |
Notes | none |
Example
<AuhState
class="ch.nevis.esauth.auth.states.oauth2.AuthorizationServer"
final="false"
name="AuthorizationServer"
resumeState="true">
<ResultCond name="invalid-client" next="AuthError" />
<ResultCond name="invalid-redirect-uri" next="AuthError" />
<ResultCond name="invalid-authorization-request" next="AuthError" />
<ResultCond name="server-error" next="AuthError" />
<ResultCond name="invalid-token-request" next="AuthError" />
<ResultCond name="valid-token-request" next="AuthDone" />
<ResultCond name="valid-authorization-request" next="Login" />
<property name="client.clientId_1.clientName" value="abc" />
<property name="client.clientId_1.redirectUrl"
value="http://example.ch:8080/oauth/client/" />
<property name="client.clientId_1.scope"
value="openid email profile address phone" />
<property name="client.clientId_1.secret" value="secret" />
<property name="dataSource" value="local" />
<property name="openid.issuerId" value="https://www.nevis.ch" />
<property name="openid.support" value="true" />
<property name="scope.address" value="" />
<property name="scope.email" value="" />
<property name="scope.openid" value="" />
<property name="scope.phone" value="" />
<property name="scope.profile" value="" />
</AuthState>