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:
breakView on GitHub (pinned to a1a16a5a77)
Solutions
- Read error.cause (logged by Auth.js) to see the provider's exact error code and fix accordingly.
- Ensure AUTH_*_CLIENT_ID / AUTH_*_CLIENT_SECRET (or provider config) exactly match the values registered at the provider.
- Avoid reusing the callback URL: each authorization code is single-use; restart the sign-in flow instead of refreshing the callback page.
- Verify provider settings: redirect URI, PKCE requirement, and token_endpoint_auth_method match the library's provider definition.
- 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
- Log error.cause to capture the provider's error code on every OAuth failure
- Keep client credentials in env and validate presence at startup
- Never reuse authorization codes; restart the flow instead of refreshing callbacks
- Match PKCE and token auth method settings with your provider app configuration
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
- OAuth Provider returned an error
- Missing or invalid provider account
- Provider not supported
- Account not created
- The account is already associated with another user
AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28).
Data as JSON: /api/errors/8a8df573a488ff2c.
Report an issue: GitHub.