OAuth 2.0 Token Exchange Customization

The OAuth 2.0 Token Exchange flow adheres to the RFC 8693. However the token exchange specification is an open specification meant to solve a number of use cases with the main focus on service to service impersonation or delegation, meaning one API exchanging a token it received for another token with itself as the actor but perhaps with some other claims than the original token.

Another common use case is to exchange an access token received from another authorization server (not the Curity Identity Server) for a token issued by the Curity Identity Server, or it could be some custom token type or format that should be exchanged.

Provided all the possible scenarios that can be desirable to suppport, the OAuth 2.0 Token Exchange grant uses a slightly different structure in the token procedures and token procedure plugins.

The built in behaviour of the grant type is described in the Default OAuth 2.0 Token Exchange Behaviour section.

Introspection of provided tokens

There are two types of tokens that can be provided as input, the subject_token and the actor_token. The server will attempt to introspect these if the corresponding token types (subject_token_type and actor_token_type) is urn:ietf:params:oauth:token-type:access_token. If successful the contents of these tokens are provided to the procedure or procedure plugin in the context.

Subject Token

As stated in the introduction it is a valid use case to want to exchange access tokens issued by other OAuth server for tokens issued by the Curity Identity Server. For this reason, the server will not reject a valid request with a subject_token that it could not introspect. Instead it is the task of the procedure or the procedure plugin in that case to perform the relevant introspection and decide if the request should be rejected. If no procedure or plugin is configured the default procedure will reject the request.

When the token was not possible to introspect by the server itself (i.e. not issued by the Curity Identity Server) the getPresentedSubjectToken() will return null and the procedure is tasked with the introspection. The raw token provided by the client can be found using the getSubjectTokenValue() method in the context, along with the type of provided token using getSubjectTokenType().

Important

The request does not fail if the subject token can’t be introspected by the server, it is the task of the procedure or plugin to reject the request in that case.

Actor Token

The actor token is an optional token that can be provided in the request. It represents the party requesting the exchange. If present it indicates an impersonation or delegation flow where the act claim should be set in the resulting token. The actor token is assumed to always be a token isssued by the Curity Identity Server. If the actor_token parameter is present but the token fails introspection by the server, the request will be rejected. This differs from the subject_token which is assumed to possibly be a token issued from other servers.

Therefore the context can assume that if the getPresentedActorToken() returns null, no actor_token was presented in the request.

Important

If the actor token is present it must be possible to introspect by the server or the request will fail. It is assumed to be issued by the Curity Identity Server.

Context Initialization

Since the subject token may not have been introspected when the token procedure executes, the context can not be fully initialized at that time. Token issuers and data on the context depend on the input token to have been introspected by the server. In order to account for this in the OAuth 2.0 Token Exchange flow, there are two contexts. The context provided to the procedure or plugin as argument is an uninitialized context, that once the subject_token has been introspected can be traded for a full context.

The full context is responsible for issuing new tokens and delegations. In order for the claims subsystem to be fully accessible the audiences and scopes that are intended to be issued using the new context needs to be passed to the getInitializedContext method when initializing it. There are some built-in restrictions that are important to be aware of. If a new delegation is issued it’s important to know that the client that calls the token exchange endpoint needs to be allowed to issue the new scopes. This means that the scopes need to be configured on the client that has the OAuth 2.0 token exchange capability.

The following is an example of initialization using a token procedure

Listing 193 Example of an OAuth 2.0 Token Exchange procedure expecting an introspected token
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function result(context) {
    var presentedSubjectToken = context.getPresentedSubjectToken();
    var presentedDelegation = context.getPresentedSubjectTokenDelegation();

    if (presentedSubjectToken === null) {
        throw exceptionFactory.badRequestException("invalid_request", "Invalid subject_token");
    }

    var scopes = presentedSubjectToken.get("scope").split(" ");

    var fullContext = context.getInitializedContext(context.subjectAttributes(), context.contextAttributes(), presentedSubjectToken.get("aud"), scopes);

...

If the token is not assumed to be issued by the Curity Identity Server the token procedure will look as follows

Listing 194 Example of an OAuth 2.0 Token Exchange procedure expecting an introspected token
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 function result(context) {

    var subjectTokenValue = context.getSubjectTokenValue();

    var myIntrospectionResult = myGoodInstrospection(subjectTokenValue);
    var subjectAttributes = myIntrospectionResult.getSubjectAttributes();
    var audienceList = myIntrospectionResult.getAudience();
    var scopesToBeIssued = ["scope1", "scope2"];

    var fullContext = context.getInitializedContext(subjectAttributes, null, audienceList, scopesToBeIssued);

    ...

Note

It is not possible to issue tokens using the context before the context has been initialized.