docs(oidc): add back-channel logout (#9034)

# Which Problems Are Solved

OIDC Back-Channel Logout released with
[V2.65.0](https://github.com/zitadel/zitadel/releases/tag/v2.65.0) were
not yet documented

# How the Problems Are Solved

- Added small guide and description
- Updated claims (added `sid` and `events`)

# Additional Changes

None

# Additional Context

relates to https://github.com/zitadel/zitadel/issues/8467
This commit is contained in:
Livio Spring 2024-12-13 10:47:04 +01:00 committed by GitHub
parent 0a859fe416
commit 40fedace3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 246 additions and 63 deletions

View File

@ -6,67 +6,72 @@ sidebar_label: Claims
ZITADEL asserts claims on different places according to the corresponding specifications or project and clients settings.
Please check below the matrix for an overview where which scope is asserted.
| Claims | Userinfo | Introspection | ID Token | Access Token |
| :------------------------------------------------ | :------------- | --------------------------------------- | ------------------------------------------- | ---------------------------------------------------- |
| acr | No | No | Yes | No |
| act | No | After Token Exchange with `actor_token` | After Token Exchange with `actor_token` | When JWT and after Token Exchange with `actor_token` |
| address | When requested | When requested | When requested and response_type `id_token` | No |
| amr | No | No | Yes | No |
| aud | No | Yes | Yes | When JWT |
| auth_time | No | No | Yes | No |
| azp (client_id when Introspect) | No | Yes | Yes | When JWT |
| email | When requested | When requested | When requested and response_type `id_token` | No |
| email_verified | When requested | When requested | When requested and response_type `id_token` | No |
| exp | No | Yes | Yes | When JWT |
| family_name | When requested | When requested | When requested and response_type `id_token` | No |
| gender | When requested | When requested | When requested and response_type `id_token` | No |
| given_name | When requested | When requested | When requested and response_type `id_token` | No |
| iat | No | Yes | Yes | When JWT |
| iss | No | Yes | Yes | When JWT |
| jti | No | Yes | No | When JWT |
| locale | When requested | When requested | When requested and response_type `id_token` | No |
| name | When requested | When requested | When requested and response_type `id_token` | No |
| nbf | No | Yes | Yes | When JWT |
| nonce | No | No | Yes | No |
| phone | When requested | When requested | When requested and response_type `id_token` | No |
| phone_verified | When requested | When requested | When requested and response_type `id_token` | No |
| preferred_username (username when Introspect) | When requested | When requested | Yes | No |
| sub | Yes | Yes | Yes | When JWT |
| urn:zitadel:iam:org:domain:primary:\{domainname} | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:org:project:roles | When requested | When requested | When requested or configured | When JWT and requested or configured |
| urn:zitadel:iam:user:metadata | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:id | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:name | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:primary_domain | When requested | When requested | When requested | When JWT and requested |
| Claims | Userinfo | Introspection | ID Token | Access Token |
|:--------------------------------------------------|:---------------|-----------------------------------------|-------------------------------------------------|------------------------------------------------------|
| acr | No | No | Yes | No |
| act | No | After Token Exchange with `actor_token` | After Token Exchange with `actor_token` | When JWT and after Token Exchange with `actor_token` |
| address | When requested | When requested | When requested and response_type `id_token` | No |
| amr | No | No | Yes | No |
| aud | No | Yes | Yes | When JWT |
| auth_time | No | No | Yes | No |
| azp (client_id when Introspect) | No | Yes | Yes | When JWT |
| email | When requested | When requested | When requested and response_type `id_token` | No |
| email_verified | When requested | When requested | When requested and response_type `id_token` | No |
| exp | No | Yes | Yes | When JWT |
| family_name | When requested | When requested | When requested and response_type `id_token` | No |
| gender | When requested | When requested | When requested and response_type `id_token` | No |
| given_name | When requested | When requested | When requested and response_type `id_token` | No |
| iat | No | Yes | Yes | When JWT |
| iss | No | Yes | Yes | When JWT |
| jti | No | Yes | No | When JWT |
| locale | When requested | When requested | When requested and response_type `id_token` | No |
| name | When requested | When requested | When requested and response_type `id_token` | No |
| nbf | No | Yes | Yes | When JWT |
| nonce | No | No | When provided in the authorization request [^1] | No |
| phone | When requested | When requested | When requested and response_type `id_token` | No |
| phone_verified | When requested | When requested | When requested and response_type `id_token` | No |
| preferred_username (username when Introspect) | When requested | When requested | Yes | No |
| sid | No | No | Yes | No |
| sub | Yes | Yes | Yes | When JWT |
| urn:zitadel:iam:org:domain:primary:\{domainname} | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:org:project:roles | When requested | When requested | When requested or configured | When JWT and requested or configured |
| urn:zitadel:iam:user:metadata | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:id | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:name | When requested | When requested | When requested | When JWT and requested |
| urn:zitadel:iam:user:resourceowner:primary_domain | When requested | When requested | When requested | When JWT and requested |
[^1]: The nonce can also be used to distinguish between an id_token and a logout_token as latter must never include a nonce.
## Standard Claims
| Claims | Example | Description |
| :----------------- | :------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| acr | TBA | TBA |
| act | `{"iss": "$CUSTOM-DOMAIN","sub": "259241944654282754"}` | JSON object describing the actor from the `actor_token` after [token exchange](/docs/guides/integrate/token-exchange#actor-token) |
| address | `Lerchenfeldstrasse 3, 9014 St. Gallen` | TBA |
| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176) <br/> `password` value is deprecated, please check `pwd` |
| aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included |
| auth_time | `1311280969` | Unix time of the authentication |
| azp | `69234237810729234` | Client id of the client who requested the token |
| email | `road.runner@acme.ch` | Email Address of the subject |
| email_verified | `true` | Boolean if the email was verified by ZITADEL |
| exp | `1311281970` | Time the token expires (as unix time) |
| family_name | `Runner` | The subjects family name |
| gender | `other` | Gender of the subject |
| given_name | `Road` | Given name of the subject |
| iat | `1311280970` | Time of the token was issued at (as unix time) |
| iss | `$CUSTOM-DOMAIN` | Issuing domain of a token |
| jti | `69234237813329048` | Unique id of the token |
| locale | `en` | Language from the subject |
| name | `Road Runner` | The subjects full name |
| nbf | `1311280970` | Time the token must not be used before (as unix time) |
| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client |
| phone | `+41 79 XXX XX XX` | Phone number provided by the user |
| phone_verified | `true` | Boolean if the phone was verified by ZITADEL |
| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` |
| sub | `77776025198584418` | Subject ID of the user |
| Claims | Example | Description |
|:-------------------|:---------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| acr | TBA | TBA |
| act | `{"iss": "$CUSTOM-DOMAIN","sub": "259241944654282754"}` | JSON object describing the actor from the `actor_token` after [token exchange](/docs/guides/integrate/token-exchange#actor-token) |
| address | `Lerchenfeldstrasse 3, 9014 St. Gallen` | TBA |
| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176) <br/> `password` value is deprecated, please check `pwd` |
| aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included |
| auth_time | `1311280969` | Unix time of the authentication |
| azp | `69234237810729234` | Client id of the client who requested the token |
| email | `road.runner@acme.ch` | Email Address of the subject |
| email_verified | `true` | Boolean if the email was verified by ZITADEL |
| events | `{ "http://schemas.openid.net/event/backchannel-logout": {} }` | Security Events such as Back-Channel Logout |
| exp | `1311281970` | Time the token expires (as unix time) |
| family_name | `Runner` | The subjects family name |
| gender | `other` | Gender of the subject |
| given_name | `Road` | Given name of the subject |
| iat | `1311280970` | Time of the token was issued at (as unix time) |
| iss | `$CUSTOM-DOMAIN` | Issuing domain of a token |
| jti | `69234237813329048` | Unique id of the token |
| locale | `en` | Language from the subject |
| name | `Road Runner` | The subjects full name |
| nbf | `1311280970` | Time the token must not be used before (as unix time) |
| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client |
| phone | `+41 79 XXX XX XX` | Phone number provided by the user |
| phone_verified | `true` | Boolean if the phone was verified by ZITADEL |
| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` |
| sid | `291693710356251044` | String identifier for a session. This represents a session of a user agent for a logged-in end-User. Different sid values are used to identify distinct sessions at an OP. |
| sub | `77776025198584418` | Subject ID of the user |
## Custom Claims
@ -100,12 +105,12 @@ https://github.com/zitadel/actions/blob/main/examples/custom_roles.js#L20-L33
ZITADEL reserves some claims to assert certain data. Please check out the [reserved scopes](scopes#reserved-scopes).
| Claims | Example | Description |
| :------------------------------------------------ | :------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| urn:zitadel:iam:action:\{actionname}:log | `{"urn:zitadel:iam:action:appendCustomClaims:log": ["test log", "another test log"]}` | This claim is set during Actions as a log, e.g. if two custom claims with the same keys are set. |
| urn:zitadel:iam:org:domain:primary:\{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the primary domain of the organization the user belongs to. |
|:--------------------------------------------------|:---------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| urn:zitadel:iam:action:\{actionname}:log | `{"urn:zitadel:iam:action:appendCustomClaims:log": ["test log", "another test log"]}` | This claim is set during Actions as a log, e.g. if two custom claims with the same keys are set. |
| urn:zitadel:iam:org:domain:primary:\{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the primary domain of the organization the user belongs to. |
| urn:zitadel:iam:org:project:roles | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on the current project (where your client belongs to). |
| urn:zitadel:iam:org:project:\{projectid}:roles | `{"urn:zitadel:iam:org:project:id3:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on a specific project. |
| urn:zitadel:iam:roles:\{rolename} | TBA | TBA |
| urn:zitadel:iam:org:project:\{projectid}:roles | `{"urn:zitadel:iam:org:project:id3:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on a specific project. |
| urn:zitadel:iam:roles:\{rolename} | TBA | TBA |
| urn:zitadel:iam:user:metadata | `{"urn:zitadel:iam:user:metadata": [ {"key": "VmFsdWU=" } ] }` | The metadata claim will include all metadata of a user. The values are base64 encoded. |
| urn:zitadel:iam:user:resourceowner:id | `{"urn:zitadel:iam:user:resourceowner:id": "orgid"}` | This claim represents the id of the resource owner organisation of the user. |
| urn:zitadel:iam:user:resourceowner:name | `{"urn:zitadel:iam:user:resourceowner:name": "ACME"}` | This claim represents the name of the resource owner organisation of the user. |

View File

@ -0,0 +1,177 @@
---
title: OIDC Back-Channel Logout
sidebar_label: Back-Channel Logout
---
The Back-Channel Logout implements [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html)
and can be used to notify clients about session termination at the OpenID Provider. This guide will explain how
back-channel logout is implemented inside ZITADEL and gives some usage examples.
:::info
Back-Channel Logout is currently an experimental [beta](/docs/support/software-release-cycles-support#beta) feature.
Be sure to enable it on the [feature API](#feature-api) before using it.
We highly recommend also enabling the `webkey` feature on your instance. This will prevent issues with automatic key rotation.
:::
In this guide we assume your already familiar with getting and validation token. You should already have a good
understanding on the following topics before starting with this guide:
- Integrate your app with the [OIDC flow](/docs/guides/integrate/login/oidc/login-users) to obtain tokens
- [Claims](/docs/apis/openidoauth/claims)
- [Scope](/docs/apis/openidoauth/scopes)
- Audience
## Concept
ZITADEL provides the possibility for OpenID Connect clients to be notified about the session termination, for example
if a user signs out from another client using the same SSO session.
This allows the client to also invalidate the user's session without the need for an active browser session.
![Authentication and Back-Channel Logout Flow](/img/guides/back-channel-logout/back-channel-logout-flow.png)
1. When an unauthenticated user visits your application,
2. it will create an authorization request to the authorization endpoint.
3. The Authorization Server (ZITADEL) will send an HTTP 302 to the user's browser, which will redirect them to the login UI.
4. The user will have to authenticate using the demanded auth mechanics.
5. Your application will be called on the registered callback path (redirect_uri) for the authorization code exchange.
See [OIDC Flow](/docs/guides/integrate/login/oidc/login-users) for more details.
On successful exchange, an SSO session will be created.
6. If the user opens another application,
7. the application will also create an authorization request to the authorization endpoint.
8. ZITADEL can then reuse the existing SSO session and will not ask the user to authenticate again and directly return the code for exchange.
See [OIDC Flow](/docs/guides/integrate/login/oidc/login-users) again for details.
9. At a later point, the user signs out from one of the applications, in this case the second one.
10. The application will redirect the user to the end_session endpoint.
11. ZITADEL will terminate the SSO session and redirect the user back to the application's post_logout_redirect_uri.
The application can delete the local session.
12. ZITADEL will also send a back-channel logout request to every registered application with previously opened sessions.
The application can then invalidate the user's session without the need for an active browser session.
### Indicating Support
As required by the [specification](https://openid.net/specs/openid-connect-backchannel-1_0.html#BCSupport), ZITADEL
will advertise the `backchannel_logout_supported` and `backchannel_logout_session_supported`
on the discovery endpoint once the feature flag is enabled.
The latter boolean indicates, that ZITADEL will provide a session ID (`sid`) claim as part of the logout token. This
provides the possibility to match the exact SSO session, which was terminated.
### Client
To enable the back-channel logout on a client, they simply need to register a `backchannel_logout_uri` as part of their
configuration, e.g. [creating an OIDC application](https://zitadel.com/docs/apis/resources/mgmt/management-service-add-oidc-app).
As soon as the URI is set, every new authorization request will register a back-channel notification to be sent once
the session is terminated by a user sign out.
## Back-Channel Request
When the session is terminated, ZITADEL will send back-channel logout requests asynchronously to every registered
client of the corresponding session.
The request is an `application/x-www-form-urlencoded` POST request to the registered URI including a `logout_token`
parameter in the body.
Please be aware that body *may* contain other values in addition to logout_token. Values that are not understood by the
implementation *must* be ignored.
### Logout Token
The `logout_token` sent in the request is a JWT similar to an ID Token.
:::info
Note however, that a Logout Token must never contain a `nonce` claim, to make sure it cannot be used as an ID Token.
:::
The following Claims are used within the Logout Token:
| Claim | Example | Description |
|--------|----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| iss | `$CUSTOM-DOMAIN` | Issuer Identifier |
| sub | `77776025198584418` | Subject Identifier (the user who signed out) |
| aud | `69234237810729019` | Audience(s), will always contain your client_id |
| iat | `1311280970` | Issued at time |
| exp | `1311281970` | Expiration time (by default 15min after the issued at time) |
| jti | `69234237813329048` | Unique identifier for the token |
| events | `{ "http://schemas.openid.net/event/backchannel-logout": {} }` | JSON object, which always contains http://schemas.openid.net/event/backchannel-logout. This declares that the JWT is a Logout Token. |
| sid | `291693710356251044` | Session ID - String identifier for a Session. |
#### Validation
Verify the Logout Token the same way you verify an ID Token including signature validation, issuer, audience, expiration and issued at time claim checks.
Make sure that either a subject (`sub`) and / or a session ID (`sid`) is present in the token to identify the user or its session.
Also, check that the `events` claim contains the `http://schemas.openid.net/event/backchannel-logout` value.
Optionally, you can also verify that the TokenID (`jti`) has not been used before.
For details on how to validate the logout token, please refer to the [OpenID Connect Back-Channel Logout specification](https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation).
## Example
This example assumes that your application is registered with client_id `243864426485212395@example` and the back-channel logout
URI is set to `https://example.com/logout`.
### Authentication
When the user signed in to your application, ZITADEL issued the following id_token:
```
eyJhbGciOiJSUzI1NiIsImtpZCI6IjI5NjkzMzA1NjAxNzY0Nzg3NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJzdWIiOiIxNjQ4NDk0OTQyOTc0NzczNzciLCJhdWQiOlsiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiLCIyNDM4NjQzODExNjk4ODY0NDMiXSwiZXhwIjoxNzMzNzgyNDQ0LCJpYXQiOjE3MzM3MzkyNDQsImF1dGhfdGltZSI6MTczMzczOTI0NCwiYW1yIjpbInVzZXIiLCJtZmEiXSwiYXpwIjoiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiLCJjbGllbnRfaWQiOiIyNDM4NjQ0MjY0ODUyMTIzOTVAZXhhbXBsZSIsImF0X2hhc2giOiJSWVFPSkJuT01LS0hrN1VnLWY1eFJnIiwic2lkIjoiVjFfMjk3MzY0ODE4OTgwMDM0MDA0In0K.lZxHE_Z4tiaDQE-DPtYjnvb0H9rz4wMoGfBMeEm4EG837DGJb7RTq7PuMHWc4Z2e_6lilwfVBWDEOhmrnjmkQwDVxInbbJfN0NiWgeqoW-C1SZ_G00UVIbJdaxPy2-haRihDNNpy0Gjmi7q3FkGXGqkJx9S7ZtC5ISbXLnqfbRbuapoMs7hHNf-Iltf8v7dMs3K8dcAPSHJm0X0x6Cu1ZMeAS2a6H05xKXGM0bRK830AZlL8xmxTNj_q_WZKzxz304XrRNHvYRcHKmJqURSHvRNUR38QeNaiKzINlV2sVvPEY6Dru_PHSPNFu7YLWiUi34VUla6VTxy9ctI_BtI4nw
```
This represents the following claims:
```json
{
"iss": "http://localhost:8080",
"sub": "164849494297477377",
"aud": [
"243864426485212395@example",
"243864381169886443"
],
"exp": 1733782444,
"iat": 1733739244,
"auth_time": 1733739244,
"amr": [
"user",
"mfa"
],
"azp": "243864426485212395@example",
"client_id": "243864426485212395@example",
"at_hash": "RYQOJBnOMKKHk7Ug-f5xRg",
"sid": "V1_297364818980034004"
}
```
As you can see, the `sid` claim is present in the token. This represents the application's specific session ID of the
user session.
### Sign Out and Back-Channel Logout
When the user signs out from another application, ZITADEL will send a POST request to `https://example.com/logout` with
the following body:
```http
POST /logout HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
logout_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjI5NjkzMzA1NjAxNzY0Nzg3NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJzdWIiOiIxNjQ4NDk0OTQyOTc0NzczNzciLCJhdWQiOlsiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiXSwiaWF0IjoxNzMzNzM5MjUyLCJleHAiOjE3MzM3NDAxNTMsImp0aSI6IjI5NzM2NDgzNDQ5ODkyMTEzNyIsImV2ZW50cyI6eyJodHRwOi8vc2NoZW1hcy5vcGVuaWQubmV0L2V2ZW50L2JhY2tjaGFubmVsLWxvZ291dCI6e319LCJzaWQiOiJWMV8yOTczNjQ4MTg5ODAwMzQwMDQifQo.ELOPuS61fy8GgCKEtru5df4-9GI4-KQlNf_DMp6b5mtJZIrfykA_M7lYOxOskYhTDicBoQ2jjOjzsDqktI4r6ptD068c5LEOx-k2OVk7ybADsK7tht5omYy4tsbHmkDCZN065WMH0SQH7NKGroVW-MACi6Peuiz3nlQsfho0EnLECqhZT60qxu6qtofvBVhHe15Zlkzffy0vxjKEIeJoTmX_cNVsHlrC_n1vTqZStBrkqu3_rwZxZuynX47vf7_kj_kKhJ3TffRF561n1AP5xnhZ9i--rnaucbGtGGImlKi2sdqC4GzjtdlaKJaRuVF-91x758SLBxqJXPucroJoWw
```
```json
{
"iss": "http://localhost:8080",
"sub": "164849494297477377",
"aud": [
"243864426485212395@example"
],
"iat": 1733739252,
"exp": 1733740153,
"jti": "297364834498921137",
"events": {
"http://schemas.openid.net/event/backchannel-logout": {}
},
"sid": "V1_297364818980034004"
}
```
After validating the token, the application can now invalidate the user's local session based on the `sid` claim.
Some applications might also want to delete all user sessions. In this case, the `sub` claim can be used to identify the user.

View File

@ -258,6 +258,7 @@ module.exports = {
],
},
"guides/integrate/token-exchange",
"guides/integrate/back-channel-logout",
{
type: "category",
label: "Service Users",

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB