OAuth2 protection of resource server content, is typically either done via a call to the authorization service (AS) and the ../introspect endpoint for stateful access_tokens, or, in deployments where stateless access_tokens are deployed, the resource server (RS) could perform “local” introspection, if they have access to the necessary AS signing material. All good. The RS would valid scope values, token expiration time and so on.
Contrast that to the typical externalised authorization model, with a policy enforcement point (PEP) and policy decision point (PDP). Something being protected, sends in a request to a central PDP. That request is likely to contain the object descriptor, a token representing the subject and some contextual data. The PDP will have a load of pre-built signatures or policies that would be looked up and processed. The net-net is the PDP sends back a deny/allow style decision which the PEP (either in the form of an SDK or a policy agent) complies with.
So what is this blog about? Well it’s the juxtaposition of the typical OAuth2 construct, with externalised PDP style authorization.
So the first step is to set up a basic policy within ForgeRock Access Management that protects a basic web URL – http://app.example.com:8080/index.html. In honesty the thing being protected could be a URL, button, image, physical object or any other schema you see fit.
|Out of the box authorization policy summary|
To call the PDP, an application would create a REST payload looking something like the following:
|REST request payload to PDP|
The request would be a POST ../openam/json/policies?_action=evaluate endpoint. This endpoint is a protected endpoint, meaning it requires authX from an AM instance. In a normal non-OAuth2 integrated scenario, this would be handled via the iPlanetDirectoryPro header that would be used within the PDP decision. Now in this case, we don’t have an iPlanetDirectoryPro cookie, simply the access_token.
Application Service Account
So, there are a couple of extra steps to take. Firstly, we need to give our calling application their own service account. Simply add a new group and associated application service user. This account could then authenticate either via shared secret, JWT, x509 or any other authentication method configured. Make sure to give the associated group the account is in, privileges to the call the REST PDP endpoint. So back to the use case…
This REST PDP request is the same as any other. We have the resource being protected which maps into the policy and the OAuth2 access_token that was generated out of band, presented to the PDP as an environment variable.
OAuth2 Validation Script
The main validation is now happening in a simple Policy Condition script. The script does a few things: performs a call to the AM ../introspect endpoint to perform basic validation – is the token AM issued, valid, within exp and so on. In addition there are two switches – perform auth_level validation and also perform scope_validation. Each of these functions takes a configurable setting. If performAuthLevelCheck is true, make sure to set the acceptableAuthLevel value. As of AM 5.5, the issued OAuth2 access_token now contains a value called “auth_level”. This value just ties in the authentication assurance level that has been in AM since the OpenSSO days. This numeric value is useful to differentiate how a user was validated during OAuth2 issuance. The script basically allows a simple way to perform a minimum acceptable value check.
The other configurable switch, is the performScopeCheck boolean. If true, the script checks to make sure that the submitted access_token, is associated with atleast a minimum set of required scopes. The access_token may have more scopes, but it must, as a minimum have the ones configured in the acceptableScopes attribute.
Once the script is in place lets run through some examples where access is denied. The first simple one is if the auth_level of the access_token is less than the configured acceptable value.
|acceptable_auth_level_not_met advice message|
Next up, is the situation where the scopes associated with the submitted access_token fall short of what is required. There are two advice payloads that could be sent back here. Firstly, if the number of scopes is fundamentally too small, the following advice is sent back:
|acceptable_scopes_not_met – submitted scopes too few|
A second response, associated with mismatched scopes, is if the number of scopes is OK, but the actual values don’t contain the acceptable ones. The following is seen:
|acceptable_scopes_not_met – scope entry missing|
That’s all there is to it. A few things to know. The TTL of the policy has been set to be the exp of the access_token. Clearly this is over writable, but seemed sensible to tie this to the access_token lifespan.
All being well though, a successful response back would look something like the following – depending on what actions you had configured in your policy:
|Successful PDP response|
Augmenting with Additional Environmental Conditions
So we have an OAuth2-compatible PDP. Cool! But what else can we do. Well, we can augment the scripted decision making, with a couple of other conditions. Namely the time based, IP address based and LDAP based conditions.
|IP and Time based augmentation of access_token validation|
The above just shows a simple of example of tying the decision making to only allow valid access_token usage between 830am and 530pm Monday-Friday from a valid IP range. The other condition worth a mention is the LDAP filter one.
Note, any of the environmental conditions that require session validation, would fail, the script isn’t linking any access_token to AM session at this point – in some cases (depending on how the access_token was generated) may never have a session associated. So beware they will not work.
The code for the scripted condition is available here.