Skip to main content

Customizing claims with OAuth2 webhooks

You can modify aspects of the OpenID Connect and access tokens returned from Hydra's OAuth2 token endpoint. A typical use case is adding custom claims to the tokens issued by Ory OAuth2/Ory Hydra.

Customize the token response by registering a webhook endpoint in your OAuth2 configuration. Before the token is issued to the client, Ory will call your HTTPS endpoint with information about the OAuth client requesting the token.

When performing an Authorization Code flow information about the resource owner's session is also included in the webhook payload.

Your endpoint's response to the webhook will be used to customize the token that Ory issues to the OAuth client, and optionally overwrite the session data stored for the resource owner.

Using webhooks is supported for all grant types (flows).

note

The webhook is called before any other logic is executed. If the webhook execution fails -- for example if your endpoint is unreachable or responds with an HTTP error code -- the token exchange will fail for the OAuth client.

Configuration

Use the Ory CLI to register your webhook endpoint:

ory patch oauth2-config --project <project-id> --workspace <workspace-id> \
--add '/oauth2/token_hook/url="https://my-example.app/token-hook"' \
--add '/oauth2/token_hook/auth/type="api_key"' \
--add '/oauth2/token_hook/auth/config/in="header"' \
--add '/oauth2/token_hook/auth/config/name="X-API-Key"' \
--add '/oauth2/token_hook/auth/config/value="MY API KEY"' \
--format yaml

Webhook payload

Ory will perform a POST request with a JSON payload towards your endpoint.

Example OAuth2 token webhook request payload
{
"session": {
"id_token": {
"id_token_claims": {
"jti": "",
"iss": "http://your-slug-xyz.projects.oryapis.com",
"sub": "subject",
"aud": ["app-client"],
"nonce": "",
"at_hash": "",
"acr": "1",
"amr": null,
"c_hash": "",
"ext": {}
},
"headers": {
"extra": {}
},
"username": "",
"subject": "foo"
},
"extra": {},
"client_id": "app-client",
"consent_challenge": "",
"exclude_not_before_claim": false,
"allowed_top_level_claims": []
},
"request": {
"client_id": "app-client",
"granted_scopes": ["offline", "openid", "hydra.*"],
"granted_audience": [],
"grant_types": ["authorization_code"],
"payload": {
"assertion": ["eyJhbGciOiJIUzI..."]
}
}
}

session represents the OAuth2 session, along with the data that was passed to the Accept Consent Request in the id_token field (only applicable to Authorization code flows).

request contains information from the OAuth client's request to the token endpoint.

The request.payload.assertion field will be populated for flows of the urn:ietf:params:oauth:grant-type:jwt-bearer grant type only, and contains the JWT the client passed as the assertion in their call to the token endpoint.

Responding to the webhook

When handling the webhook in your endpoint, use the request payload to decide how Ory should proceed in the token exchange with the client.

To accept the token exchange without modification, return a 204 or 200 HTTP status code without a response body.

To deny the token exchange, reply with a 403 HTTP status code.

To modify the claims of the issued tokens and instruct Hydra to proceed with the token exchange, return 200 with a JSON response body:

{
"session": {
"access_token": {
"your:custom:access-token-claim": "any value you like",
"your:second:access-token-claim": 124390123
},
"id_token": {
"your:custom:id-token-claim": "another value",
"your:second:id-token-claim": 2394123
}
}
}

Responding with any other HTTP status code will abort the token exchange toward the OAuth2 client with an error message.

Updated tokens

Tokens issued by Ory to the OAuth2 client will contain the data from your webhook response:


{
"aud": [
"my_client"
],
"auth_time": 1647427485,
"your:custom:id-token-claim": "another value",
"your:second:id-token-claim": 2394123,
"iss": "http://ory.hydra.example/",
"sub": "[email protected]"
}

note

You cannot override the token subject.

Refresh token

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

Legacy webhook implementation (deprecated)

danger

This mechanism is deprecated and no longer supported

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:

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

Legacy 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:

{
"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.