nextauthjs/next-auth · error · AccessDenied
AccessDenied
Error message
AccessDenied
What it means
AccessDenied with message 'AccessDenied' is thrown by handleAuthorized when the `signIn` callback in the auth config resolves to a falsy value (false, null, undefined, 0, ''). The signIn callback is the application's authorization gate; returning falsy tells Auth.js the user is not allowed to proceed, and the framework converts that into an AccessDenied error that propagates to the client as an access-denied result.
Source
Thrown at packages/core/src/lib/actions/callback/index.ts:549
} catch (e) {
if (e instanceof AuthError) throw e
const error = new CallbackRouteError(e as Error, { provider: provider.id })
logger.debug("callback route error details", { method, query, body })
throw error
}
}
async function handleAuthorized(
params: Parameters<InternalOptions["callbacks"]["signIn"]>[0],
config: InternalOptions
): Promise<string | undefined> {
let authorized
const { signIn, redirect } = config.callbacks
try {
authorized = await signIn(params)
} catch (e) {
if (e instanceof AuthError) throw e
throw new AccessDenied(e as Error)
}
if (!authorized) throw new AccessDenied("AccessDenied")
if (typeof authorized !== "string") return
return await redirect({ url: authorized, baseUrl: config.url.origin })
}
View on GitHub (pinned to a1a16a5a77)
Solutions
- Review the `callbacks.signIn` implementation and ensure intended paths return true (or a redirect URL)
- Log the user/profile inside signIn to see which condition evaluates falsy in production
- If the rejection is intended, handle the AccessDenied / accessdenied error or ?error=AccessDenied redirect on the client instead of treating it as a bug
- Update allowlists or rules so legitimate users pass the gate
Example fix
// before
callbacks: {
signIn: async ({ user }) => {
if (user.email?.endsWith("@corp.com")) return true
// falls through -> undefined -> AccessDenied
}
}
// after
callbacks: {
signIn: async ({ user }) => {
if (user.email?.endsWith("@corp.com")) return true
return false // explicit, and handle the denial client-side
}
} Defensive patterns
Strategy: validation
Validate before calling
// config-time check: every branch of signIn must return
const signInCb = callbacks.signIn
if (signInCb) {
const r = await signInCb({ user: mockUser, account: mockAccount } as any)
console.assert(r !== undefined, "signIn callback must return true/false/url")
} Type guard
function isAllowed(r: unknown): r is true | string {
return r === true || typeof r === "string"
} Try / catch
try {
await signIn(providerId)
} catch (e) {
if (e instanceof AccessDenied) {
// user was rejected by your signIn callback
redirect("/auth/denied")
}
} Prevention
- Ensure every code path in callbacks.signIn returns a value (true/false/URL)
- Test signIn callback against both allowed and denied users
- Check ?error=AccessDenied redirects in the client UI
- Log the user profile inside signIn when diagnosing production denials
When it happens
Trigger: Any sign-in flow (oauth, credentials, email, webauthn) where the configured `callbacks.signIn` returns false or another falsy value — e.g. an allowlist check failing, a banned user, or an accidentally missing return statement in the callback.
Common situations: Domain allowlist (only @company.com emails) rejecting a personal Gmail account; signIn callback with early-return branches that fall through without a return; conditional logic that returns false in production but passed in dev; users testing with uninvited accounts.
Related errors
AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28).
Data as JSON: /api/errors/b94b41bb3172f7ce.
Report an issue: GitHub.