mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-04 22:39:09 -06:00
Documentation for Authorization Chaining Across Domains
Closes #45466 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/guides/images/jwt-authorization-grant-oidc-add-scope.png
Normal file
BIN
docs/guides/images/jwt-authorization-grant-oidc-add-scope.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/guides/images/jwt-authorization-grant-oidc-clienta.png
Normal file
BIN
docs/guides/images/jwt-authorization-grant-oidc-clienta.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/guides/images/jwt-authorization-grant-oidc-clientb.png
Normal file
BIN
docs/guides/images/jwt-authorization-grant-oidc-clientb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/guides/images/jwt-authorization-grant-oidc-idp.png
Normal file
BIN
docs/guides/images/jwt-authorization-grant-oidc-idp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -3,7 +3,7 @@
|
||||
|
||||
<@tmpl.guide
|
||||
title="Securing applications with DPoP"
|
||||
priority=140
|
||||
priority=150
|
||||
summary="Guide for securing applications with DPoP using Keycloak">
|
||||
|
||||
Standard OAuth 2.0 access tokens are typically **Bearer** tokens. Any party in possession of the token can use it to access protected resources, regardless of whether they are the legitimate client. If a Bearer token is leaked (e.g., via server logs, network interception, or browser storage), it is vulnerable to unauthorized reuse, known as a replay attack.
|
||||
|
||||
@@ -133,7 +133,7 @@ NOTE: Following the spec recommendation, the JWT Authorization Grant never issue
|
||||
|
||||
== How to get a valid token for JWT Authorization Grant
|
||||
|
||||
The JWT Authorization Grant feature needs a previous JWT assertion in order to be exchanged for an access token. We can call the external OpenID Connect Provider (OP) `domaina`, the one that is represented in {project_name} via the Identity Provider. And we can call the {project_name} server that receives the JWT authorization grant `domainb`. The `domaina` should somehow issue a JWT that is a valid assertion for `domainb`.
|
||||
The JWT Authorization Grant feature needs a previous JWT assertion in order to be exchanged for an access token. We can name the external OpenID Connect Provider (OP) `domaina`, the one that is represented in {project_name} via the Identity Provider. And we can name the {project_name} server that receives the JWT authorization grant `domainb`. The `domaina` should somehow issue a JWT that is a valid assertion for `domainb`.
|
||||
|
||||
If `domaina` is a server different to {project_name}, we don't know how that initial JWT is obtained. But note that the specification enforces some processing of the assertion to be valid and return the access token. The way the client gets or generates such a JWT assertion in `domaina` depends completely on `domaina` server and client.
|
||||
|
||||
@@ -142,65 +142,7 @@ In case the external identity provider is another {project_name} server or realm
|
||||
. For `domaina`, `domainb` is an audience that can be restricted via token exchange.
|
||||
. For `domainb`, `domaina` is an Identity Provider that is used to validate the assertion. The user in `domainb` is also a valid user previously linked to `domaina` via the identity provider.
|
||||
|
||||
For example, if `domaina` is a {project_name} realm, the following configuration is available to use Token Exchange to obtain the JWT assertion:
|
||||
|
||||
. Create a client that represents `domainb`. This client can be the same client configured in `domainb` for the identity provider access.
|
||||
* **Client ID**: http://localhost:8080/realms/domainb (issuer for `domainb`).
|
||||
* **Name**: domainb
|
||||
+
|
||||
.Client that represents domainb in domaina
|
||||
image::jwt-authorization-grant-oidc-client-domaina.png[Client that represents domainb in domaina]
|
||||
. 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.
|
||||
* **Name**: access-domainb
|
||||
* **Type**: None
|
||||
* Only enable **Include in token scope**.
|
||||
+
|
||||
.Client scope to include domainb as audience
|
||||
image::jwt-authorization-grant-oidc-client-scope-domaina.png[Client scope to include domainb as audience]
|
||||
* In the client scope add a mapper:
|
||||
** **Type**: Audience
|
||||
** **Name**: domainb-audience
|
||||
** **Included Client Audience**: http://localhost:8080/realms/domainb
|
||||
** **Add to access token**: On
|
||||
+
|
||||
.Client scope mapper for domainb audience
|
||||
image::jwt-authorization-grant-oidc-client-scope-mapper.png[Client scope mapper for domainb audience]
|
||||
. In the client used to do the token exchange assign the previous `access-domainb` client scope as optional.
|
||||
|
||||
NOTE: 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.
|
||||
|
||||
With the previous configuration, the client in `domaina` can request a token exchange including scope `access-domainb` and restricting the audience to `http://localhost:8080/realms/domainb` (issuer of `domainb`).
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
POST /realms/domaina/protocol/openid-connect/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Accept: application/json
|
||||
|
||||
client_id=clienta&
|
||||
client_secret=XXXXX&
|
||||
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
|
||||
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
|
||||
requested_token_type=urn:ietf:params:oauth:token-type:access_token&
|
||||
scope=access-domainb&
|
||||
audience=http://localhost:8080/realms/domainb&
|
||||
subject_token=$SUBJECT_TOKEN
|
||||
----
|
||||
|
||||
The resulting token will be a valid token for JWT Authorization Grant in `domainb`. So another client defined for `domainb` can send a JWT Authorization Grant with that assertion.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
POST /realms/domainb/protocol/openid-connect/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Accept: application/json
|
||||
|
||||
client_id=clientb&
|
||||
client_secret=YYYYYY&
|
||||
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&
|
||||
scope=scope1 scope2
|
||||
assertion=$ASSERTION_TOKEN
|
||||
----
|
||||
See <@links.securingapps id="oauth-identity-authorization-chaining-across-domains" /> for a detailed configuration to perform authorization chaining across two {project_name} realms.
|
||||
|
||||
== Client policies and JWT Authorization Grant
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
<#import "/templates/guide.adoc" as tmpl>
|
||||
<#import "/templates/links.adoc" as links>
|
||||
<#import "/templates/features.adoc" as features>
|
||||
|
||||
<@tmpl.guide
|
||||
title="OAuth Identity and Authorization Chaining Across Domains"
|
||||
priority=140
|
||||
summary="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 link:https://datatracker.ietf.org/doc/html/draft-ietf-oauth-identity-chaining/[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 link:https://datatracker.ietf.org/doc/html/rfc8693[OAuth 2.0 Token Exchange (RFC8693)] and the link:https://datatracker.ietf.org/doc/html/rfc7523[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
|
||||
image::identity-authorization-chaining.dio.svg[Identity and Authorization Chaining Flow]
|
||||
|
||||
. The client in trust domain A discovers the location of the authorization server of trust domain B.
|
||||
. 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.
|
||||
. 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.
|
||||
. The client in trust domain A presents the authorization grant to the authorization server of trust domain B.
|
||||
. Authorization server of trust domain B validates the JWT authorization grant and returns an access token.
|
||||
. 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.
|
||||
|
||||
{project_name} currently provides standard Token Exchange (see <@links.securingapps id="token-exchange" anchor="_standard-token-exchange" />) as a supported feature and JWT Authorization Grant (see <@links.securingapps id="jwt-authorization-grant" />) as technology preview. This way, {project_name} 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.
|
||||
|
||||
WARNING: 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 <@links.securingapps id="token-exchange" anchor="_standard-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 ID**: http://localhost:8080/realms/domainb (issuer for `domainb`).
|
||||
* **Name**: domainb
|
||||
* Enable **Client authentication**. The credential will be used to configure the Identity Provider in `domainb`.
|
||||
* **Valid redirect URIs**: http://localhost:8080/realms/domainb/broker/domaina/endpoint/* (IdP endpoint in `domainb`)
|
||||
* **Valid post logout redirect URIs**: `+`
|
||||
|
||||
.Client that represents domainb in domaina
|
||||
image::jwt-authorization-grant-oidc-client-domaina.png[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
|
||||
image::jwt-authorization-grant-oidc-client-scope-domaina.png[Client scope to include domainb as audience]
|
||||
|
||||
In the **Mappers** tab, click **Configure a new mapper** and select **Audience**:
|
||||
|
||||
* **Mapper type**: Audience
|
||||
* **Name**: domainb-audience
|
||||
* **Included Client Audience**: http://localhost:8080/realms/domainb
|
||||
* **Add to access token**: On
|
||||
|
||||
.Client scope mapper for domainb audience
|
||||
image::jwt-authorization-grant-oidc-client-scope-mapper.png[Client scope mapper for domainb audience]
|
||||
|
||||
NOTE: 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
|
||||
image::jwt-authorization-grant-oidc-clienta.png[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
|
||||
image::jwt-authorization-grant-oidc-add-scope.png[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_token_exchange]]
|
||||
=== 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`.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
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`).
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
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 <@links.securingapps id="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.
|
||||
|
||||
* **Alias**: domaina
|
||||
* **Discovery endpoint**: http://localhost:8080/realms/domaina/.well-known/openid-configuration (load OpenID metadata from the discovery endpoint of `domaina`)
|
||||
* **Client ID**: http://localhost:8080/realms/domainb (client created in `domaina` that represents `domainb`)
|
||||
* **Client Secret**: <copy the secret for the client>
|
||||
|
||||
.Identity Provider for domaina
|
||||
image::jwt-authorization-grant-oidc-idp.png[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
|
||||
image::jwt-authorization-grant-oidc-clientb.png[Client for JWT authorization grant]
|
||||
|
||||
=== Link the sample user in `domainb`
|
||||
|
||||
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 security** -> **Linked accounts** that the user is correctly linked to `domaina`.
|
||||
|
||||
=== Example request for JWT authorization grant
|
||||
|
||||
The `token2` obtained in <<_example_token_exchange,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.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
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`.
|
||||
|
||||
</@tmpl.guide>
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<@tmpl.guide
|
||||
title="Specifications implemented"
|
||||
priority=140
|
||||
priority=160
|
||||
summary="List of specifications and standards implemented by {project_name}.">
|
||||
|
||||
This {section} presents a list of specifications and standards that {project_name} currently implements. The standards are separated in different sections and, in each one, a table is shown with the following four columns:
|
||||
|
||||
Reference in New Issue
Block a user