OAuth2 webhooks
Ory OAuth2 and OpenID Connect 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.
The hook is called before any other logic is executed. If the hook execution fails, the entire token flow fails.
Configuration
You can use hooks feature with the following grant types:
- authorization_code
- client_credentials
- refresh_token
- jwt-bearer- see RFC7523
Use the Ory CLI with following keys to enable this feature:
- authorization_code
- client_credentials_hook
- refresh_token_hook
- jwt-bearer
ory patch oauth2-config {project.id} \
  --add "/oauth2/authorization_code_hook=\"https://my-example.app/authorization-code-hook\"" \
  --format yaml
ory patch oauth2-config {project.id} \
  --add "/oauth2/client_credentials_hook=\"https://my-example.app/client-credentials-hook\"" \
  --format yaml
ory patch oauth2-config {project.id} \
  --add "/oauth2/refresh_token_hook=\"https://my-example.app/token-refresh-hook\"" \
  --format yaml
ory patch oauth2-config {project.id} \
  --add "/oauth2/jwt_bearer_hook=\"https://my-example.app/jwt-bearer-hook\"" \
  --format yaml
Webhook payload
The 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"],
    "payload": {}
  },
  "granted_scopes": ["openid", "offline"],
  "granted_audience": []
}
session represents the consent session, along with the data that was passed to the
Accept Consent Request in the id_token field.
requester is the token request context.
Requester payload
For client_credentials and jwt-bearer grant types, the entire payload that you send to the /token endpoint will also be sent
to the configured webhook URL.
Here's the format of the requester.payload field for each grant type:
- client_credentials
- jwt-bearer
{
  "grant_type": [
    "client_credentials"
  ],
  "audience": ["my-api"],
  "scope": ["user:profile:read"]
}
    
{
  "grant_type": [
    "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
  ],
  "assertion": ["eyJhbGciOiJIUzI..."],
  "scope": ["user:profile:read"]
}
     
For authorization_code and refresh_token grant types, the requester.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:
{
  "session": {
    "access_token": {
      "foo": "bar"
    },
    "id_token": {
      "bar": "baz"
    }
  }
}
Alternatively, you can choose not to update the session data by returning a 204 No Content response.
The token subject is never overridden.
Updated tokens
The following examples show fragments of tokens issued after the webhook call:
- id_token
- access_token
{
  "aud": [
    "my_client"
  ],
  "auth_time": 1647427485,
  "bar": "baz",
  "iss": "http://ory.hydra.example/",
  "sub": "foo@bar.com"
}
    
{
  "active": true,
  "scope": "openid offline",
  "client_id": "my_client",
  "sub": "foo@bar.com",
  "aud": [],
  "iss": "http://ory.hydra.example/",
  "token_type": "Bearer",
  "token_use": "access_token",
  "ext": {
      "foo": "bar"
    }
}
     
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 flow.
Refresh token
If a webhook for refresh_token grant type fails with a non-graceful result, the refresh flow also fails and the supplied
refresh_token remains unused.