# OAuth callback security

> Understand the shared Credential authorization callback, tenant routing, signed state, PKCE, and replay protection.

Standard interactive CredentialProviders share one callback:

```text
https://platform.constal.ai/v1/credential-authorizations/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 authorization session.
2. Constal signs state containing the exact Credential CRN, session ID, version, and expiry.
3. The CredentialCoordinator 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 routes to the correct tenant-owned coordinator.
8. The coordinator matches the one-time session and state hash, then invokes the pinned provider's completion 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}

Authorization 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 authorization from the Credential page instead of replaying a saved callback URL.

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

The current public callback accepts standard OAuth-style GET query parameters. Device flows do not use it. Providers requiring POST callbacks, OAuth 1.0, SAML responses, or another ingress shape need an explicitly supported authorization ingress rather than overloading this endpoint.
