nextauthjs/next-auth · error · OAuthCallbackError

OAuth Provider returned an error

Error message

OAuth Provider returned an error

What it means

When validating the OAuth redirect response, openid-client may raise AuthorizationResponseError (e.g. the provider redirected back with error=access_denied, a missing/mismatched state, or an invalid code). Auth.js catches it inside handleOAuth and rethrows it as OAuthCallbackError('OAuth Provider returned an error') with the provider id and response error entries as the cause.

Source

Thrown at packages/core/src/lib/actions/callback/oauth/callback.ts:147

  const state = await checks.state.use(cookies, resCookies, options)

  let codeGrantParams: URLSearchParams
  try {
    codeGrantParams = o.validateAuthResponse(
      as,
      client,
      new URLSearchParams(params),
      provider.checks.includes("state") ? state : o.skipStateCheck
    )
  } catch (err) {
    if (err instanceof o.AuthorizationResponseError) {
      const cause = {
        providerId: provider.id,
        ...Object.fromEntries(err.cause.entries()),
      }
      logger.debug("OAuthCallbackError", cause)
      throw new OAuthCallbackError("OAuth Provider returned an error", cause)
    }
    throw err
  }

  const codeVerifier = await checks.pkce.use(cookies, resCookies, options)

  let redirect_uri = provider.callbackUrl
  if (!options.isOnRedirectProxy && provider.redirectProxyUrl) {
    redirect_uri = provider.redirectProxyUrl
  }

  let codeGrantResponse = await o.authorizationCodeGrantRequest(
    as,
    client,
    clientAuth,
    codeGrantParams,
    redirect_uri,
    codeVerifier ?? "decoy",

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Inspect the OAuthCallbackError cause (entries like error, error_description, state) to read the provider's actual rejection reason
  2. If error=access_denied, the user declined consent — handle it gracefully in the UI, no code change needed
  3. Fix cookie/state issues: ensure NEXTAUTH_URL/AUTH_URL matches the origin, cookies are not blocked, and the auth secret is stable across instances
  4. Re-authorize the app / request provider approval scopes if the provider rejects unregistered redirect URIs or unapproved apps

Example fix

// before
// no error handling: user sees raw failure after canceling consent
// after
try {
  await signIn("github")
} catch (e) {
  if (e instanceof OAuthCallbackError) {
    // e.cause.error === "access_denied" etc.
    showToast("Sign-in was canceled or rejected by the provider")
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before redirecting to the provider, confirm callback URL and cookies are sane
if (!window.cookiesEnabled) console.warn("State cookie may be dropped; sign-in can fail")

Type guard

function isOAuthCallbackError(e: unknown): e is OAuthCallbackError {
  return e instanceof OAuthCallbackError
}

Try / catch

try {
  await signIn(providerId)
} catch (e) {
  if (isOAuthCallbackError(e)) {
    const { error, error_description } = e.cause ?? {}
    // access_denied, invalid_state, etc. — surface to user or log
  }
}

Prevention

When it happens

Trigger: The provider redirects to the callback URL with an error parameter (user denied consent, app not approved); the state/PKCE cookie is missing or mismatched (blocked cookies, cross-domain redirect, multiple tabs); the authorization code is invalid, expired, or already redeemed.

Common situations: User cancels the consent screen at the provider; NEXTAUTH_URL/auth secret mismatch across environments causing state cookie decryption failures; multiple sign-in tabs overwriting state cookies; mobile in-app browsers stripping cookies; provider app in development mode rejecting unregistered testers.

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/fea827929a8b2fcf. Report an issue: GitHub.