nextauthjs/next-auth · error · OAuthCallbackError

OAuth Provider returned an error: ${responseJson.error}

Error message

OAuth Provider returned an error: ${responseJson.error}

What it means

handleOAuth throws OAuthCallbackError when the OAuth token endpoint's code-grant response JSON contains an `error` field, meaning the provider rejected the authorization-code exchange. The provider's error payload (and providerId) is attached as `cause` for diagnostics. This surfaces provider-side rejections such as invalid codes or bad client credentials.

Source

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

  const requireIdToken = isOIDCProvider(provider)

  if (provider[conformInternal]) {
    switch (provider.id) {
      case "microsoft-entra-id":
      case "azure-ad": {
        /**
         * These providers return errors in the response body and
         * need the authorization server metadata to be re-processed
         * based on the `id_token`'s `tid` claim.
         * @see: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow#error-response-1
         */
        const responseJson = await codeGrantResponse.clone().json()
        if (responseJson.error) {
          const cause = {
            providerId: provider.id,
            ...responseJson,
          }
          throw new OAuthCallbackError(
            `OAuth Provider returned an error: ${responseJson.error}`,
            cause
          )
        }
        const { tid } = decodeJwt(responseJson.id_token)
        if (typeof tid === "string") {
          const tenantRe = /microsoftonline\.com\/(\w+)\/v2\.0/
          const tenantId = as.issuer?.match(tenantRe)?.[1] ?? "common"
          const issuer = new URL(as.issuer.replace(tenantId, tid))
          const discoveryResponse = await o.discoveryRequest(issuer, {
            [o.customFetch]: provider[customFetch],
          })
          as = await o.processDiscoveryResponse(issuer, discoveryResponse)
        }
        break
      }
      default:
        break

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Read error.cause (logged by Auth.js) to see the provider's exact error code and fix accordingly.
  2. Ensure AUTH_*_CLIENT_ID / AUTH_*_CLIENT_SECRET (or provider config) exactly match the values registered at the provider.
  3. Avoid reusing the callback URL: each authorization code is single-use; restart the sign-in flow instead of refreshing the callback page.
  4. Verify provider settings: redirect URI, PKCE requirement, and token_endpoint_auth_method match the library's provider definition.
  5. Check for clock skew between your server and the provider if errors are intermittent (expired codes).

Example fix

// before: client auth mismatch causes invalid_client
Auth0Provider({ clientId: process.env.AUTH_AUTH0_ID })
// after: supply secret correctly and set the right auth method
Auth0Provider({
  clientId: process.env.AUTH_AUTH0_ID,
  clientSecret: process.env.AUTH_AUTH0_SECRET,
  authorization: { params: { scope: "openid email profile" } },
})
Defensive patterns

Strategy: try-catch

Validate before calling

const id = process.env.AUTH_AUTH0_ID, secret = process.env.AUTH_AUTH0_SECRET
if (!id || !secret) throw new Error("Missing OAuth client credentials")

Type guard

function hasProviderError(r: unknown): r is { error: string } {
  return typeof r === "object" && r !== null && "error" in r
}

Try / catch

try {
  await signIn("provider", { redirectTo: "/" })
} catch (e) {
  if (e instanceof OAuthCallbackError) {
    console.error("Provider rejected token exchange:", e.cause)
  }
}

Prevention

When it happens

Trigger: During the sign-in callback, POSTing the authorization code to the provider's token endpoint returns 200/4xx JSON like {"error":"invalid_grant"} or {"error":"invalid_client"}; handleOAuth detects responseJson.error and throws.

Common situations: Authorization code already consumed (user refreshed the callback URL or double-submitted the callback); code expired; mismatched client_id/client_secret between issuer config and env vars; provider requires PKCE or client auth method (client_secret_post vs basic) the config doesn't use; clock skew invalidating tokens.

Related errors


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