OAuth2ResourceServerFilter
Validates a request containing an OAuth 2.0 access token. The filter expects an
OAuth 2.0 token from the HTTP Authorization header of the request, such as the
following example header, where the OAuth 2.0 access token is 1fc…ec9
:
Authorization: Bearer 1fc...ec9
The filter performs the following tasks:
-
Extracts the access token from the request header.
-
Uses the configured access token resolver to resolve the access token against an authorization server, and validate the token claims.
-
Checks that the token has the scopes required by the filter configuration.
-
Injects the access token info into the OAuth2Context.
The following errors can occur during access token validation:
Error | Response from the filter to the user-agent |
---|---|
Combination of the filter configuration and access token result in an invalid request to the authorization server. |
|
There is no access token in the request header. |
|
The access token isn’t valid, for example, because it has expired. |
|
The access token doesn’t have all of the scopes required in the OAuth2ResourceServerFilter configuration. |
|
Usage
{
"name": string,
"type": "OAuth2ResourceServerFilter",
"config": {
"accessTokenResolver": AccessTokenResolver reference,
"cache": object,
"executor": Executor service reference,
"requireHttps": configuration expression<boolean>,
"realm": configuration expression<string>,
"scopes": [ runtime expression<string>, ... ] or ScriptableResourceAccess reference
}
}
An alternative value for type is OAuth2RSFilter.
Properties
"accessTokenResolver"
: AccessTokenResolver reference, required-
Resolves an access token against an authorization server. Configure one of the following access token resolvers:
To decorate an AccessTokenResolver, add the decoration at the
accessTokenResolver
level. The following example uses the default timer decorator to record the time that a TokenIntrospectionAccessTokenResolver takes to process a request:{ "accessTokenResolver": { "type": "TokenIntrospectionAccessTokenResolver", "config": { ... }, "timer": true } }
"cache"
: object, optional-
Configuration of caching for OAuth 2.0 access tokens. By default, access tokens are not cached. For an alternative way of caching of OAuth 2.0 access tokens, configure CacheAccessTokenResolver.
When an access token is cached, IG can reuse the token information without repeatedly asking the authorization server to verify the access token. When caching is disabled, IG must ask the authorization server to verify the access token for each request.
(From AM 6.5.3.) When an access_token is revoked on AM, the CacheAccessTokenResolver can delete the token from the cache when both of the following conditions are true:
-
The
notification
property of AmService is enabled. -
The delegate AccessTokenResolver provides the token metadata required to update the cache.
When a refresh_token is revoked on AM, all associated access tokens are automatically and immediately revoked.
"cache": { "enabled": configuration expression<boolean>, "defaultTimeout": configuration expression<duration>, "maxTimeout": configuration expression<duration>, "amService": AmService reference, "onNotificationDisconnection": configuration expression<enumeration> }
enabled
: configuration expression<boolean>, optional-
Enable or disable caching.
Default:
false
defaultTimeout
: configuration expression<duration>, optional-
The duration for which to cache an OAuth 2.0 access token if it doesn’t provide a valid expiry value.
If an access token provides an expiry value that falls before the current time plus the
maxTimeout
, IG uses the token expiry value.The following example caches access tokens for these times:
-
One hour, if the access token doesn’t provide a valid expiry value.
-
The duration specified by the token expiry value, when the token expiry value is shorter than one day.
-
One day, when the token expiry value is longer than one day.
"cache": { "enabled": true, "defaultTimeout": "1 hour", "maxTimeout": "1 day" }
Default:
1 minute
-
maxTimeout
: configuration expression<duration>, optional-
The maximum duration for which to cache OAuth 2.0 access tokens.
If an access token provides an expiry value that falls after the current time plus the
maxTimeout
, IG uses themaxTimeout
.The duration cannot be
zero
orunlimited
. "amService"
: AmService reference, optional-
(From AM 6.5.3.) The AmService to use for the WebSocket notification service. To evict revoked access tokens from the cache, enable the
notifications
property of AmService. onNotificationDisconnection
: configuration expression<enumeration>, optional-
An
amService
must be configured for this property to have effect.The strategy to manage the cache when the WebSocket notification service is disconnected, and IG receives no notifications for AM events. If the cache is not cleared it can become outdated, and IG can allow requests on revoked sessions or tokens.
Cached entries that expire naturally while the notification service is disconnected are removed from the cache.
Use one of the following values:
-
NEVER_CLEAR
-
When the notification service is disconnected:
-
Continue to use the existing cache.
-
Deny access for requests that are not cached, but do not update the cache with these requests.
-
-
When the notification service is reconnected:
-
Continue to use the existing cache.
-
Query AM for incoming requests that are not found in the cache, and update the cache with these requests.
-
-
-
CLEAR_ON_DISCONNECT
-
When the notification service is disconnected:
-
Clear the cache.
-
Deny access to all requests, but do not update the cache with these requests.
-
-
When the notification service is reconnected:
-
Query AM for all requests that are not found in the cache. (Because the cache was cleared, the cache is empty after reconnection.)
-
Update the cache with these requests.
-
-
-
CLEAR_ON_RECONNECT
-
When the notification service is disconnected:
-
Continue to use the existing cache.
-
Deny access for requests that are not cached, but do not update the cache with these requests.
-
-
When the notification service is reconnected:
-
Query AM for all requests that are not found in the cache. (Because the cache was cleared, the cache is empty after reconnection.)
-
Update the cache with these requests.
-
-
Default:
CLEAR_ON_DISCONNECT
-
-
"executor"
: Executor service reference, optional-
An executor service to schedule the execution of tasks, such as the eviction of entries in the access token cache.
Default:
ScheduledExecutorService
See also ScheduledExecutorService.
"requireHttps"
: configuration expression<boolean>, optional-
Whether to require that original target URI of the request (
originalUri
in UriRouterContext) uses the HTTPS scheme.If the request received by the web container is not using HTTPS, the request is rejected.
Default: true.
"realm"
: configuration expression<string>, optional-
HTTP authentication realm to include in the WWW-Authenticate response header field when returning an HTTP 401 Unauthorized status to a user-agent that need to authenticate.
Default: OpenIG
"scopes"
: array of runtime expression<strings> or ScriptableResourceAccess <reference>, required-
A list of one of more scopes required by the OAuth 2.0 access token.
- array of runtime expression<strings>, required if ScriptableResourceAccess is not used
-
A string, array of strings, runtime expression<string>, or array of runtime expression<string> to represent one or more scopes.
- ScriptableResourceAccess <reference>, required if "array of runtime expression<strings>" is not used
-
A script that produces a list of one or more scopes.
The script evaluates each request dynamically and returns the scopes that request needs to access the protected resource. The script must return a Promise<Set, ResponseException> or a Set<String>.
For information about the properties of ScriptableResourceAccess, see Scripts.
{ "name": string, "type": "ScriptableResourceAccess", "config": { "type": configuration expression<string>, "file": configuration expression<string>, // Use either "file" "source": [ string, ... ], // or "source", but not both. "args": object, "clientHandler": Handler reference } }
Default: Empty
Examples
For examples using OAuth2ResourceServerFilter, see Act as an OAuth 2.0 resource server.
The following example uses a ScriptableResourceAccess object to define the scopes required in a request:
-
If the request path is
/rs-tokeninfo
, only the scopesmail
is required. -
If the request path is
/rs-tokeninfo/employee
, the scopesmail
andemployeenumber
are required.
-
Set up and test the example in Validate access tokens through the introspection endpoint.
-
Add the following route to IG:
-
Linux
-
Windows
$HOME/.openig/config/routes/rs-dynamicscope.json
%appdata%\OpenIG\rs-dynamicscope.json
{ "name": "rs-dynamicscope", "baseURI": "http://app.example.com:8081", "condition": "${find(request.uri.path, '^/rs-dynamicscope')}", "heap": [ { "name": "SystemAndEnvSecretStore-1", "type": "SystemAndEnvSecretStore" }, { "name": "AmService-1", "type": "AmService", "config": { "agent": { "username": "ig_agent", "passwordSecretId": "agent.secret.id" }, "secretsProvider": "SystemAndEnvSecretStore-1", "url": "http://am.example.com:8088/openam/", "version": "7.2" } } ], "handler": { "type": "Chain", "config": { "filters": [ { "name": "OAuth2ResourceServerFilter-1", "type": "OAuth2ResourceServerFilter", "config": { "scopes": { "name": "myscript", "type": "ScriptableResourceAccess", "config": { "type": "application/x-groovy", "source": [ "// Minimal set of required scopes", "def scopes = [ 'mail' ] as Set", "if (request.uri.path =~ /employee$/) {", " // Require another scope to access this resource", " scopes += 'employeenumber'", "}", "return scopes" ] } }, "requireHttps": false, "realm": "OpenIG", "accessTokenResolver": { "name": "token-resolver-1", "type": "TokenIntrospectionAccessTokenResolver", "config": { "amService": "AmService-1", "providerHandler": { "type": "Chain", "config": { "filters": [ { "type": "HttpBasicAuthenticationClientFilter", "config": { "username": "ig_agent", "passwordSecretId": "agent.secret.id", "secretsProvider": "SystemAndEnvSecretStore-1" } } ], "handler": "ForgeRockClientHandler" } } } } } } ], "handler": { "type": "StaticResponseHandler", "config": { "status": 200, "headers": { "Content-Type": [ "text/html; charset=UTF-8" ] }, "entity": "<html><body><h2>Decoded access_token: ${contexts.oauth2.accessToken.info}</h2></body></html>" } } } } }
-
-
Test the setup with the
mail
scope only:-
In a terminal, use a
curl
command to retrieve an access token:$ mytoken=$(curl -s \ --user "client-application:password" \ --data "grant_type=password&username=demo&password=Ch4ng31t&scope=mail" \ http://am.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
-
Confirm that the access token is returned for the
/rs-dynamicscope
path:$ curl -v http://ig.example.com:8080/rs-dynamicscope --header "Authorization: Bearer ${mytoken}" { active = true, scope = mail, client_id = client-application, user_id = demo, token_type = Bearer, exp = 158...907, sub = demo, iss = http://am.example.com:8088/openam/oauth2, ... ... }
-
Confirm that the access token is not returned for the
/rs-dynamicscope/employee
path:$ curl -v http://ig.example.com:8080/rs-dynamicscope/employee --header "Authorization: Bearer ${mytoken}"
-
-
-
Test the setup with the scopes
mail
andemployeenumber
:-
In a terminal window, use a
curl
command similar to the following to retrieve an access token:$ mytoken=$(curl -s \ --user "client-application:password" \ --data "grant_type=password&username=demo&password=Ch4ng31t&scope=mail%20employeenumber" \ http://am.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
-
Confirm that the access token is returned for the
/rs-dynamicscope/employee
path:$ curl -v http://ig.example.com:8080/rs-dynamicscope/employee --header "Authorization: Bearer ${mytoken}"
-