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

  1. Review the `callbacks.signIn` implementation and ensure intended paths return true (or a redirect URL)
  2. Log the user/profile inside signIn to see which condition evaluates falsy in production
  3. If the rejection is intended, handle the AccessDenied / accessdenied error or ?error=AccessDenied redirect on the client instead of treating it as a bug
  4. 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

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.