# access_denied on callback

## The error

The browser lands on your callback URL with `?error=access_denied&error_description=...`. The user authenticated fine; something refused to issue the authorization.

## Read the description first

- "User did not consent" or consent prompt denied: the user clicked Deny on the consent screen. Third-party apps always show consent; first-party apps show it when the API's "Allow Skipping User Consent" is off or YOUR_HOST is involved.
- Email domain or signup policy: an Action or Rule denied the login (e.g. `api.access.deny("end_users_not_allowed")`). The description often carries the custom message.
- Connection-level denial: enterprise connections can deny based on IdP claims.

## Confirm in tenant logs

Dashboard > Monitoring > Logs. Filter `type:f` (failed login) around the timestamp. The log entry's `details.error` repeats the description and names the connection and client. If a Rule/Action denied it, the log shows the extensibility point.

## Fix by cause

- Consent denied by the user: nothing to fix; handle the error in your callback route with a friendly page, not a stack trace.
- Consent showing when it should not: Dashboard > Applications > APIs > your API > enable "Allow Skipping User Consent" for first-party apps. Note consent cannot be skipped for YOUR_HOST or third-party clients.
- Action/Rule denial: read the deny reason, adjust the policy (domain allowlist, role check) in the Action, redeploy, and re-test.
- Wrong connection: the app may be offering a database connection to users who should use SSO. Limit the app's enabled connections or pass the `connection` parameter.

## Checklist

- Callback route handles error params gracefully.
- The deny reason is identified in logs before changing policy.