OAuth

Fix NextAuth / Auth.js OAuth Errors — invalid_grant, CORS, Redirect URI

Updated April 2026

Reading this? Verify your fix live. Debug your OAuth error → OAuthFixer

NextAuth.js (now Auth.js) adds an abstraction layer over OAuth. When things break, the error message often points at the library rather than the underlying OAuth issue. Here is how to read through it.

The most common Auth.js errors

Error: OAuthCallbackError — "State" mismatch

# Cause: NEXTAUTH_URL does not match your actual deployment URL
# The state parameter is validated against the originating URL

# Fix — set in your deployment environment:
NEXTAUTH_URL=https://yourapp.com # no trailing slash
NEXTAUTH_SECRET=your-secret-here # required in production

Error: invalid_grant from provider

# Auth.js exchanges the code immediately — but if NEXTAUTH_URL is wrong,
# the redirect_uri in the exchange does not match the registered URI

# Debug: check what redirect_uri Auth.js is sending
# It uses: NEXTAUTH_URL + "/api/auth/callback/[provider]"
# This must EXACTLY match what you registered in the provider dashboard

# Example for Google:
# Registered: https://yourapp.com/api/auth/callback/google
# NEXTAUTH_URL: https://yourapp.com  → callback = https://yourapp.com/api/auth/callback/google ✅

# Wrong:
# NEXTAUTH_URL: https://yourapp.com/ → callback = https://yourapp.com//api/auth/callback/google ❌

Error: CORS on /api/auth endpoints

# Auth.js API routes are Next.js API routes — they handle their own CORS
# You should NOT add CORS headers to /api/auth/* routes
# If you see CORS errors on /api/auth, the issue is usually:
# 1. Calling Auth.js endpoints from a different domain (wrong architecture)
# 2. A middleware intercepting and stripping headers

# Correct: Auth.js endpoints are called from the same domain as your frontend
# Wrong: Calling /api/auth from a separate frontend app on a different domain

Auth.js v5 (Next.js App Router) setup

// auth.ts
import NextAuth from "next-auth";
import Google from "next-auth/providers/google";

export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [ Google({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }), ],
});

// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;

Required environment variables

# .env.local (development)
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-development-secret

# Production (Vercel environment variables)
NEXTAUTH_URL=https://yourapp.com
NEXTAUTH_SECRET=your-production-secret  # generate with: openssl rand -base64 32

# Provider credentials
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

Provider callback URLs to register

ProviderCallback URL to register
Googlehttps://yourapp.com/api/auth/callback/google
GitHubhttps://yourapp.com/api/auth/callback/github
Discordhttps://yourapp.com/api/auth/callback/discord
Auth0https://yourapp.com/api/auth/callback/auth0
Oktahttps://yourapp.com/api/auth/callback/okta

Debug mode

// auth.ts — enable debug logging
export const { handlers, auth } = NextAuth({ debug: process.env.NODE_ENV === "development", providers: [...],
});

Debug mode logs the full OAuth flow including the redirect URIs being used. Run locally with your production environment variables to reproduce production-only errors.

Debug your OAuth error → OAuthFixer