Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update webhook documentation with more grant types #1241

Merged
merged 4 commits into from
Apr 25, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 130 additions & 39 deletions docs/hydra/guides/updating-claims-at-refresh.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: claims-at-refresh
title: Hook into the OAuth2 refresh token flow
title: Adding custom claims to tokens
sidebar_label: OAuth2 webhooks
---

Expand All @@ -12,90 +12,101 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem'
```

Ory OAuth2 and OpenID Connect comes with a mechanism that allows updating `id_token` and `access_token` when a registered client
sends a token refresh request. The flow is realized by calling the defined refresh token hook endpoint which returns updated data.
The Hydra OAuth2 and OpenID Connect server comes with a mechanism that allows updating `id_token` and `access_token` when a
registered client sends a token request. The flow is realized by calling the defined token hook endpoint which returns updated
data.

If the data provided by the webhook is different from the data the client sends, the webhook overwrites the session data with a
new set.

:::note

The hook is called before any other logic is executed. If the hook execution fails, the entire token refresh flow fails and the
`refresh_token` remains unused.
The hook is called before any other logic is executed. If the hook execution fails, the entire token flow fails.

:::

## Configuration

Use the Ory CLI with key `/oauth2/refresh_token_hook` to enable this feature:
You can use hooks feature with all grant types.

```shell title="Enable the refresh token hook"
Use the Ory CLI with following keys to enable this feature:

```shell title="Enable the generic token hook"
ory patch oauth2-config {project.id} \
--add "/oauth2/refresh_token_hook=\"https://my-example.app/token-refresh-hook\"" \
--add "/oauth2/token_hook=\"https://my-example.app/token-hook\"" \
--format yaml
```

### Webhook payload

The refresh token hook endpoint must accept the following payload format:
The token hook endpoint must accept the following payload format:

```json
{
"subject": "foo",
"client_id": "bar",
"session": {
"id_token": {
"id_token_claims": {
"jti": "jti",
"jti": "",
"iss": "http://localhost:4444/",
"sub": "foo",
"aud": ["bar"],
"iat": 1234567,
"exp": 1234567,
"rat": 1234567,
"auth_time": 1234567,
"sub": "subject",
"aud": ["app-client"],
"nonce": "",
"at_hash": "",
"acr": "1",
"amr": [],
"amr": null,
"c_hash": "",
"ext": {}
},
"headers": {
"extra": {
"kid": "key-id"
}
"extra": {}
},
"username": "username",
"subject": "foo",
"expires_at": 1234567
"username": "",
"subject": "foo"
},
"extra": {},
"client_id": "bar",
"client_id": "app-client",
"consent_challenge": "",
"exclude_not_before_claim": false,
"allowed_top_level_claims": [],
"kid": "key-id"
"allowed_top_level_claims": []
},
"requester": {
"client_id": "bar",
"granted_scopes": ["openid", "offline"],
"request": {
"client_id": "app-client",
"granted_scopes": ["offline", "openid", "hydra.*"],
"granted_audience": [],
"grant_types": ["refresh_token"]
},
"granted_scopes": ["openid", "offline"],
"granted_audience": []
"grant_types": ["authorization_code"],
"payload": {}
}
}
```

:::note

`session` represents the consent session, along with the data that was passed to the
[Accept Consent Request](https://www.ory.sh/docs/hydra/reference/api#operation/acceptConsentRequest) in the `id_token` field.
`requester` is the token request context.
`request` is the token request context.

:::

### Requester payload

For `jwt-bearer` grant type the `assertion` value is also sent to the webhook URL.

Here's the format of the `request.payload:

```json
{
"assertion": ["eyJhbGciOiJIUzI..."]
}
```

:::note

For `authorization_code` and `refresh_token` grant types, the `request.payload` is always empty.

:::

#### Webhook responses

To update the data, the webhook must return a `200 OK` response and the updated session data in the following format:

```json
Expand All @@ -121,7 +132,7 @@ The token subject is never overridden.

### Updated tokens

The following examples show fragments of tokens issued after refreshing:
The following examples show fragments of tokens issued after the webhook call:

```mdx-code-block
<Tabs>
Expand Down Expand Up @@ -158,7 +169,87 @@ The following examples show fragments of tokens issued after refreshing:
</Tabs>
```

## Rejecting token refresh
## Rejecting token claims update

To gracefully reject token contents update, the hook must return a `403 Forbidden` response. Any other response results in a
failure of the token update and, as a result, failure of the entire token refresh flow.
failure of the token update and, as a result, failure of the entire token flow.

### Refresh token

If a webhook for `refresh_token` grant type fails with a non-graceful result, the refresh flow will also fail and the supplied
`refresh_token` will remain unused.

## Legacy webhook implementation

There is an old version of the webhook feature built specifically for the `refresh_token` grant type. We recommend using the
generic webhook feature because the old one will soon be deprecated.

Use the Ory CLI with following keys to enable this feature:

```shell title="Enable the refresh token hook"
ory patch oauth2-config {project.id} \
--add "/oauth2/refresh_token_hook=\"https://my-example.app/token-refresh-hook\"" \
--format yaml
```

### Webhook payload

The legacy webhook feature works the same way as the new one, but has a different payload that is sent to the webhook URL.

The `refresh_token` hook endpoint must accept the following payload format:

```json
{
"subject": "foo",
"client_id": "bar",
"session": {
"id_token": {
"id_token_claims": {
"jti": "jti",
"iss": "http://localhost:4444/",
"sub": "foo",
"aud": ["bar"],
"iat": 1234567,
"exp": 1234567,
"rat": 1234567,
"auth_time": 1234567,
"nonce": "",
"at_hash": "",
"acr": "1",
"amr": [],
"c_hash": "",
"ext": {}
},
"headers": {
"extra": {
"kid": "key-id"
}
},
"username": "username",
"subject": "foo",
"expires_at": 1234567
},
"extra": {},
"client_id": "bar",
"consent_challenge": "",
"exclude_not_before_claim": false,
"allowed_top_level_claims": [],
"kid": "key-id"
},
"requester": {
"client_id": "bar",
"granted_scopes": ["openid", "offline"],
"granted_audience": [],
"grant_types": ["refresh_token"]
},
"granted_scopes": ["openid", "offline"],
"granted_audience": []
}
```

:::note

If you enable both legacy and the new webhook features, both will be executed for the `refresh_token` grant type. The results of
both webhooks will be applied onto the session. In case of conflict, result of the new webhook will take priority.

:::