# OAuth callback security

> Understand how OAuth uses the shared Credential interaction callback, signed state, proof challenges, and replay protection.

Standard interactive CredentialProviders share one callback:

```text
https://platform.constal.ai/v1/credential-interactions/callback
```

The URL can be registered by multiple external applications, provider instances, and Constal tenants. Isolation comes from signed, expiring state and the pending Credential session—not from a tenant identifier in the callback path.

## Routing sequence {#routing-sequence}

1. Credential creation allocates a pending interaction session.
2. Constal signs state containing the exact Credential CRN, session ID, version, and expiry.
3. Constal encrypts the PKCE verifier, original state, owner evidence, callback URL, and temporary provider state.
4. The provider returns an authorization URL containing the same state.
5. The external service redirects to the shared callback with a code and state.
6. The platform verifies signature, version, expiry, Credential kind, and bounded parameters.
7. The Credential CRN identifies the correct tenant-owned Credential.
8. Constal matches the one-time session and state hash, then delivers a `callback` event to the pinned provider's `interaction.advance` operation.

The callback never trusts a tenant, namespace, provider, Credential, or return URL supplied as an ordinary query parameter.

## Exact callback registration {#exact-registration}

Register the callback exactly as shown. When a provider sends `redirect_uri`, its authorization and token-exchange requests must use the same URL. Do not add tenant paths, fragments, or query parameters unless that provider package explicitly defines a different callback contract.

## Provider separation {#provider-separation}

The state routes to a Credential already pinned to one installed provider. Completion therefore uses that provider instance's configuration and bootstrap Credential. Two tenants can use different external applications while sharing the callback endpoint.

## Replay and expiry {#replay-and-expiry}

Interaction sessions expire after a bounded interval. A completed session is idempotently readable but cannot exchange the code again. Expired, failed, oversized, mismatched, or unsigned callbacks are rejected. Restart the connection from the Credential page instead of replaying a saved callback URL.

## Supported callback shape {#supported-callback-shape}

The callback accepts bounded GET query parameters and bounded POST bodies. Authority-bearing request headers are removed before the event reaches provider code. Device flows normally use `display` and `wait` actions instead; protocols with a materially different ingress or trust model require an explicit platform ingress rather than overloading this endpoint.
