OAuth Identity and Authorization Chaining Across Domains

Guide for the draft about OAuth Identity and Authorization Chaining Across Domains.

Applications can require access to resources that are distributed across multiple trust domains where each trust domain has its own OAuth 2.0 authorization server. A request may transverse multiple resource servers in multiple trust domains before completing. The OAuth Identity and Authorization Chaining Across Domains is a draft specification that refers to this scenario as chaining. It defines a common pattern for combining OAuth 2.0 Token Exchange (RFC8693) and the JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants (RFC7523) to access resources across multiple trust domains while preserving identity and authorization information.

The following diagram illustrates the steps that the client in trust domain A needs to perform to access a protected resource in trust domain B.

Identity and Authorization Chaining Flow
Figure 1. Identity and Authorization Chaining Flow
  1. The client in trust domain A discovers the location of the authorization server of trust domain B.

  2. The client in trust domain A exchanges a token it has in its possession with the authorization server in trust domain A for a JWT authorization grant that can be used at the authorization server in trust domain B.

  3. The authorization server of trust domain A processes the request and returns a JWT authorization grant that the client can use with the authorization server of trust domain B.

  4. The client in trust domain A presents the authorization grant to the authorization server of trust domain B.

  5. Authorization server of trust domain B validates the JWT authorization grant and returns an access token.

  6. The client in trust domain A uses the access token received from the authorization server in trust domain B to access the protected resource in trust domain B.

Keycloak currently provides standard Token Exchange (see Configuring and using token exchange) as a supported feature and JWT Authorization Grant (see JWT Authorization Grant) as technology preview. This way, Keycloak can implement OAuth Identity and Authorization Chaining Across Domains between two different realms or servers. For domain A, domain B is an audience that can be restricted via token exchange. For domain B, domain A is an Identity Provider that is used to validate the assertion.

OAuth Identity and Authorization Chaining Across Domains is still in draft, it is not yet a definitive standard.

Configuring Token Exchange in domaina for chaining

In this sample configuration two realms, domaina and domainb, are used to represent the domains involved in the chaining diagram. This first chapter describes the steps done in domaina. With them, a client will be able to request a Token Exchange for domainb, and the resulting access token will be a valid assertion to be used for the JWT Authorization grant.

For more information about Standard Token Exchange, see Configuring and using token exchange.

Create a client that represents domainb

This client will also be the client configured in domainb for the identity provider access. Go to Clients and click Create Client.

Client that represents domainb in domaina
Figure 2. Client that represents domainb in domaina

Create a client scope to grant access to domainb

Create a client scope access-domainb to include the correct audience for the token when this scope is requested. An audience mapper for domainb will be added. In Client scopes, click Create client scope.

  • Name: access-domainb

  • Type: None

  • Only enable Include in token scope (disable Display on consent screen and Include in OpenID Provider Metadata).

Client scope to include domainb as audience
Figure 3. Client scope to include domainb as audience

In the Mappers tab, click Configure a new mapper and select Audience:

Client scope mapper for domainb audience
Figure 4. Client scope mapper for domainb audience
This example allows any user to request that client scope. Roles can be used to restrict the scope of the client scope. This way only users with a specific role would be allowed to add the domainb audience.

Create a client to perform the Token Exchange

Create a confidential OpenID Connect client clienta with Standard flow, Direct access grants and Standard token exchange capability enabled.

  • Client ID: clienta

  • Name: clienta

  • Enable Client authentication.

  • Enable capabilities Standard flow, Direct access grants and Standard token exchange.

Client for token exchange
Figure 5. Client for token exchange

In the tab Client scopes, assign the previous access-domainb scope as optional to clienta. Click Add client scope, select access-domainb and Add as Optional.

Add client scope as optional
Figure 6. Add client scope as optional

Create a sample user

Finally create a sample user in domaina with username testuser. Set a password to the account.

Example request for Token Exchange

Normally clienta will possess an initial token issued by domaina, obtained via normal authorization flow, or just because the client is a service endpoint that receives the token as a bearer. In this example, a direct access grant for testuser is requested to get token1.

curl -s -X POST \
  --location http://localhost:8080/realms/domaina/protocol/openid-connect/token \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=clienta" \
  --data-urlencode "client_secret=XXXXXX" \
  --data-urlencode "grant_type=password" \
  --data-urlencode "username=testuser" \
  --data-urlencode "password=YYYYYY"

Now clienta can exchange the token, including scope access-domainb, and restricting the audience to http://localhost:8080/realms/domainb (issuer of domainb).

curl -s -X POST \
  --location http://localhost:8080/realms/domaina/protocol/openid-connect/token \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=clienta" \
  --data-urlencode "client_secret=XXXXXX" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
  --data-urlencode "scope=access-domainb"  \
  --data-urlencode "audience=http://localhost:8080/realms/domainb" \
  --data-urlencode "subject_token=$token1"

The resulting access token token2 will be a valid assertion for JWT Authorization Grant in domainb.

Configuring JWT Authorization grant in domainb for chaining

The next steps configure domainb to accept a JWT authorization grant using the assertion obtained via Token Exchange in domaina (see previous chapter). The resulting access token will be valid for domainb.

For more information about JWT Authorization Grant, see JWT Authorization Grant.

Create an OpenID Connect Identity provider for domaina

Create a Identity Provider to establish the trust relationship with domaina. In Identity providers, click the OpenID Connect v1.0 type.

Identity Provider for domaina
Figure 7. Identity Provider for domaina

After creation, enable the JWT Authorization Grant option.

Create a client to perform the JWT authorization grant

Create a confidential OpenID Connect client clientb with JWT Authorization Grant capability enabled. In the option Allowed Identity Providers for JWT Authorization Grant add the domaina Identity Provider.

  • Client ID: clientb

  • Name: clientb

  • Enable Client authentication.

  • Enable JWT Authorization Grant capability.

  • Allowed Identity Providers for JWT Authorization Grant: domaina

Client for JWT authorization grant
Figure 8. Client for JWT authorization grant

To test the JWT Authorization Grant, the sample user created in domaina should be linked in domainb. You can just access to the account page in domainb:

  • In a browser go to the account page in domainb: http://localhost:8080/realms/domainb/account

  • Click domaina button to use the Identity Provider to login.

  • The browser is redirected to login page in domaina.

  • Log in using the test user and the password assigned previously.

  • Check in Account securityLinked accounts that the user is correctly linked to domaina.

Example request for JWT authorization grant

The token2 obtained in Example request for Token Exchange will be a valid assertion for domainb. So, the clientb defined in domainb can send a JWT Authorization Grant with that token.

curl -s -X POST \
  --location http://localhost:8080/realms/domainb/protocol/openid-connect/token \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=clientb" \
  --data-urlencode "client_secret=ZZZZZZ" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  --data-urlencode "scope=profile" \
  --data-urlencode "assertion=$token2"

The response will contain a new access token issued by domainb.

On this page