Skip to content
Open
Show file tree
Hide file tree
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
124 changes: 124 additions & 0 deletions docs/hydra/guides/oauth2-clients.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,130 @@ See [API documentation](../../reference/api#tag/oAuth2/operation/patchOAuth2Clie
</Tabs>
````

## Rotate OAuth2 client secret

OAuth2 client secret rotation allows you to change a client's secret without downtime. When you rotate a secret, the old secret
remains valid until you explicitly clean it up, allowing you to update all services using the client credentials without service
interruption.

### How secret rotation works

1. **Rotate the secret**: Generate a new secret for the client
2. **Both secrets work**: Old and new secrets both authenticate until cleanup
3. **Update services**: Update your applications to use the new secret
4. **Cleanup**: Manually remove old rotated secrets once all services are updated

### Rotate client secret

To rotate an OAuth2 client secret, use the following methods:

````mdx-code-block
<Tabs>
<TabItem value="rest" label="REST API">

```bash
curl -X POST https://{project.slug}.projects.oryapis.com/admin/clients/{client-id}/secret/rotate \
-H "Authorization: Bearer ory_pat_..."
```

The response includes the new `client_secret`. **Save this value immediately** - it will not be shown again.

See [API documentation](../../reference/api#tag/oAuth2/operation/oauth2RotateClientSecret).

</TabItem>
<TabItem value="sdk" label="Ory SDK">

```typescript
import { Configuration, OAuth2Api } from "@ory/client"

const ory = new OAuth2Api(
new Configuration({
basePath: `https://${projectSlug}.projects.oryapis.com`,
accessToken: "ory_pat_..."
})
)

const { data: client } = await ory.oauth2RotateClientSecret({
id: clientId
})

// Save the new client_secret immediately
console.log("New secret:", client.client_secret)
```

</TabItem>
</Tabs>
````

### Clear rotated secrets

Once all services have been updated to use the new secret, you can remove the old rotated secrets to revoke access using the old
credentials:

````mdx-code-block
<Tabs>
<TabItem value="rest" label="REST API">

```bash
curl -X DELETE https://{project.slug}.projects.oryapis.com/admin/clients/{client-id}/secret/rotate \
-H "Authorization: Bearer ory_pat_..."
```

After cleanup, only the current secret will be valid. Old secrets will no longer authenticate.

See [API documentation](../../reference/api#tag/oAuth2/operation/oauth2DeleteRotatedClientSecrets).

</TabItem>
<TabItem value="sdk" label="Ory SDK">

```typescript
await ory.oauth2DeleteRotatedClientSecrets({
id: clientId
})

// Old secrets are now revoked
```

</TabItem>
</Tabs>
````

### Secret rotation workflow example

Here's a complete workflow for rotating a client secret:

```bash
# 1. Get current client
CLIENT_ID="your-client-id"

# 2. Rotate the secret
NEW_SECRET=$(curl -X POST "https://{project.slug}.projects.oryapis.com/admin/clients/$CLIENT_ID/secret/rotate" \
-H "Authorization: Bearer ory_pat_..." | jq -r '.client_secret')

echo "New secret: $NEW_SECRET"

# 3. Update your applications with the new secret
# (Both old and new secrets work during this period)

# 4. Verify the new secret works
curl -X POST "https://{project.slug}.projects.oryapis.com/oauth2/token" \
-u "$CLIENT_ID:$NEW_SECRET" \
-d "grant_type=client_credentials"

# 5. Once all services are updated, clean up old secrets
curl -X DELETE "https://{project.slug}.projects.oryapis.com/admin/clients/$CLIENT_ID/secret/rotate" \
-H "Authorization: Bearer ory_pat_..."

# Old secret is now revoked
```

:::tip Zero-downtime credential rotation Secret rotation enables zero-downtime credential updates. Both the old and new secrets
remain valid until you manually clean up the rotated secrets, allowing you to update all your services without service
interruption. :::

:::warning Security best practice Rotated secrets remain valid indefinitely until you explicitly clean them up. Always remove old
rotated secrets once your migration is complete to ensure that compromised credentials cannot be used. :::

## Delete OAuth2 client

To delete an existing OAuth2 client, use the following methods:
Expand Down
86 changes: 86 additions & 0 deletions docs/reference/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -6914,6 +6914,10 @@
"format": "date-time",
"type": "string"
},
"rotated_secrets": {
"description": "OAuth 2.0 Client Rotated Secrets\n\nRotatedSecrets holds previously rotated secrets that are still valid for authentication. This allows for secret rotation without downtime. The secrets are stored in hashed format and remain valid until explicitly cleaned up.",
"type": "string"
},
"userinfo_signed_response_alg": {
"description": "OpenID Connect Request Userinfo Signed Response Algorithm\n\nJWS alg algorithm [JWA] REQUIRED for signing UserInfo Responses. If this is specified, the response will be JWT\n[JWT] serialized, and signed using JWS. The default, if omitted, is for the UserInfo Response to return the Claims\nas a UTF-8 encoded JSON object using the application/json content-type.",
"type": "string"
Expand Down Expand Up @@ -11948,6 +11952,88 @@
"tags": ["oAuth2"]
}
},
"/admin/clients/{id}/secret/rotate": {
"post": {
"description": "Rotate the client secret of an OAuth 2.0 Client. The old secret will be moved to the rotated_secrets list and will remain valid until explicitly cleaned up. This allows for zero-downtime credential rotation.\n\nThe new secret will be returned in the response. Make sure to store it securely as it will not be retrievable later.",
"operationId": "oauth2RotateClientSecret",
"parameters": [
{
"description": "OAuth 2.0 Client ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/oAuth2Client"
}
}
},
"description": "oAuth2Client"
},
"404": {
"$ref": "#/components/responses/errorOAuth2NotFound"
},
"default": {
"$ref": "#/components/responses/errorOAuth2Default"
}
},
"security": [
{
"oryAccessToken": []
}
],
"summary": "Rotate OAuth2 Client Secret",
"tags": ["oAuth2"]
},
"delete": {
"description": "Delete all rotated secrets from an OAuth 2.0 Client. This will revoke access for all previously rotated secrets, leaving only the current secret valid.\n\nUse this endpoint after you have updated all services to use the new secret following a rotation.",
"operationId": "oauth2DeleteRotatedClientSecrets",
"parameters": [
{
"description": "OAuth 2.0 Client ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/oAuth2Client"
}
}
},
"description": "oAuth2Client"
},
"404": {
"$ref": "#/components/responses/errorOAuth2NotFound"
},
"default": {
"$ref": "#/components/responses/errorOAuth2Default"
}
},
"security": [
{
"oryAccessToken": []
}
],
"summary": "Delete OAuth2 Client Rotated Secrets",
"tags": ["oAuth2"]
}
},
"/admin/courier/messages": {
"get": {
"description": "Lists all messages by given status and recipient.",
Expand Down
Loading