A complete guide to integrating Google OAuth login with Next.js + NextAuth.js. Learn step-by-step Google Cloud Console setup, fix common errors like redirect_uri_mismatch and access_denied, and avoid deployment pitfalls on Vercel. Essential for developers building one-click Google login.
Recently, I integrated Google OAuth login functionality into a project and encountered numerous pitfalls from configuration to deployment. I’ve organized my experience into this article, hoping it will help other developers facing similar challenges.
Requirement: Add “One-Click Google Login” to a web app. After users log in, their accounts should be automatically created, and they should receive bonus credits.
📋 Complete Configuration Process
Step 1: Detailed Configuration in Google Cloud Console
This is the first and most error-prone step—many developers stumble here!
1. Create a Google Cloud Project
Step 1: Access the Google Cloud ConsoleOpen the Google Cloud Console, then log in with your Google account.
Step 2: Create a new project
Click the project selector at the top of the page.
Click the “New Project” button.
Enter a project name (e.g., “my-nextjs-app”).
Select an organization (individual developers can choose “No Organization”).
Click “Create”.
💡 Tip: Use an English project name to avoid encoding issues in subsequent configurations.
2. Enable Google+ API (Critical!)
Many developers skip this step, leading to authentication failures later:
Step 1: Navigate to the API Library
In the left sidebar, go to “APIs & Services” → “Library”.
Alternatively, search directly for “Google+ API”.
Step 2: Enable Required APIsYou need to enable the following APIs:
✅ Google+ API (deprecated, but still required for certain features)
✅ People API (recommended for retrieving user information)
✅ Gmail API (only if you need access to email data)
Step 3: Enable the APIs
Click on each API card.
Click the “Enable” button.
Wait for the enablement to complete (usually takes a few seconds).
⚠️ Note: If you don’t enable these APIs, you’ll encounter an “access_denied” error later!
3. Configure the OAuth Consent Screen (Key Step)
This step determines the login authorization page users see:
Step 1: Access OAuth Consent Screen settingsIn the left sidebar, go to “APIs & Services” → “OAuth Consent Screen”.
Step 2: Select User Type
🔸 Internal: For users within your organization (requires Google Workspace).
🔸 External: For any Google user (recommended).Choose “External” if you’re an individual developer or building a public app.
Step 3: Fill in App InformationBasic Information:
App Name: The name users see on the authorization page.
User Support Email: Your contact email address.
App Logo (optional): Recommended to upload a 120x120px PNG.
App Homepage: Your app’s homepage URL.
App Privacy Policy: Link to your privacy policy page.
App Terms of Service (optional): Link to your terms of service page.
Missing test users (if the app is in Testing status).
Solutions:
Ensure the People API is enabled.
Fill in all required fields on the OAuth Consent Screen.
Add test user emails.
Error 3: invalid_client
Error Message: Error: invalid_client
Cause: Incorrect Client ID or Client Secret.
Solutions:
Verify the GOOGLE_CLIENT_ID in your environment variables.
Confirm the Client Secret was copied correctly.
Check for hidden characters or extra spaces in the values.
🔥 Pitfalls I Encountered
Pitfall 1: Server Error – Server Configuration Issue
Symptom: After clicking “Sign in with Google”, the page shows: “Server error – There is a problem with the server configuration”.
Root Cause: Incomplete or incorrect environment variable configuration.
Solution:Ensure your .env.local file includes the following:env# .env.local (required configuration) NEXTAUTH_SECRET="your_generated_secret" NEXTAUTH_URL="https://your-domain.com" GOOGLE_CLIENT_ID="your_google_client_id" GOOGLE_CLIENT_SECRET="your_google_client_secret"
Key Reminders:
Generate NEXTAUTH_SECRET using the command: openssl rand -base64 32.
Reconfigure environment variables in Vercel after deployment.
Ensure NEXTAUTH_URL matches the correct domain for local/production environments.
Pitfall 2: “This action with HTTP GET is not supported”
Symptom: The URL redirects to /api/auth/error with a “GET method not supported” message.
Key Discovery: NextAuth.js uses a fixed callback URL format: /api/auth/callback/[provider]
📋 NextAuth.js Code Configuration
Below is the complete NextAuth.js configuration file for App Router (Next.js 13+). Save it at: app/api/auth/[...nextauth]/route.ts
typescript
import {Provider} from "next-auth/providers/index";
import GoogleProvider from "next-auth/providers/google";
import {AuthOptions} from "next-auth";
import NextAuth from "next-auth";
import {genUniSeq, getIsoTimestr} from "@/backend/utils";
import {saveUser} from "@/backend/service/user";
import {User} from "@/backend/type/type";
import {createCreditUsage} from "@/backend/service/credit_usage";
import {getCreditUsageByUserId} from "@/backend/service/credit_usage";
/**
* Initialize the array of authentication providers
* Stores all available login methods (Google, GitHub, Facebook, etc.)
*/
let providers: Provider[] = [];
/**
* Configure Google OAuth provider
* Load Google app client configuration from environment variables
*/
providers.push(
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID || "", // Google app client ID
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", // Google app client secret
// 🚨 Important: Do NOT manually set redirect_uri—NextAuth.js handles this automatically
})
);
/**
* NextAuth.js configuration options
* Contains callbacks and parameters for the authentication flow
*/
const authOptions: AuthOptions = {
secret: process.env.NEXTAUTH_SECRET, // Secret for encrypting JWT tokens
providers, // List of authentication providers
callbacks: {
/**
* Sign-in verification callback
* Called when a user attempts to log in; determines if login is allowed
*/
async signIn({user, account, profile, email, credentials}) {// Add login restriction logic here (e.g., whitelist checks, ban status)
const isAllowedToSignIn = true;
if (isAllowedToSignIn) {return true;} else {
// Return false to deny login, or a URL to redirect to an error page
return false;
}
},
/**
* Redirect callback
* Controls page navigation after the authentication flow completes
*/
async redirect({url, baseUrl}) {
// Redirect to homepage after successful login
// Customize to redirect to role-specific pages if needed
return `${baseUrl}/`;
},
/**
* Session callback
* Called each time a session is fetched; customizes the session object sent to the client
*/
async session({session, token, user}) {
// Merge user data from JWT token into the session
// Allows the frontend to access full user info via useSession()
if (token && token.user) {session.user = token.user;}
return session;
},
/**
* JWT callback
* Called when a JWT token is created or updated
* Core function for user registration and database operations
*/
async jwt({token, user, account}) {// Only run on initial login (user/account params exist only on first login)
if (user && user.email && account) {
try {
// Create a database user object
const dbUser: User = {uuid: genUniSeq(), // Generate unique user ID
email: user.email, // Google account email
nickname: user.name || "", // Google account display name
avatar_url: user.image || "", // Google account avatar URL
signin_type: account.type, // Login type (oauth)
signin_provider: account.provider, // Login provider (google)
signin_openid: account.providerAccountId, // Unique Google user ID
created_at: getIsoTimestr(), // Creation time (ISO format)
signin_ip: "", // Login IP (leave empty; add IP-fetching logic later if needed)
};
// Save user to database
// The saveUser function typically handles updates if the user already exists
await saveUser(dbUser);
// Check if the user already has a credit record
const creditUsage = await getCreditUsageByUserId(dbUser.uuid);
// If new user: create a credit record and grant initial bonus credits
if (!creditUsage) {
await createCreditUsage({
user_id: dbUser.uuid, // User ID
user_subscriptions_id: -1, // Subscription ID (-1 = no subscription)
is_subscription_active: false, // Subscription status (inactive)
used_count: 0, // Number of credits used
period_remain_count: 20, // Remaining credits (20 for new users)
period_start: new Date(), // Credit period start date
period_end: new Date(// Credit period end date (1 month later)
new Date().setMonth(new Date().getMonth() + 1)
),
created_at: new Date(), // Record creation time});
}
// Store user data in JWT token
// This data will be used in the session callback later
token.user = {
uuid: dbUser.uuid,
nickname: dbUser.nickname,
email: dbUser.email,
avatar_url: dbUser.avatar_url,
created_at: dbUser.created_at,
};
} catch (error) {console.error("Error processing user data in JWT callback:", error);
// Continue authentication even if database operations fail
// Avoid disrupting the user login experience
}
}
return token;
},
},
};
/**
* Create NextAuth handler
* Initialize NextAuth with the configuration options above
*/
const handler = NextAuth(authOptions);
/**
* Export the handler
* For App Router (Next.js 13+), export both GET and POST methods
* Handles all HTTP requests for NextAuth.js
*/
export {handler as GET, handler as POST};
Discover the top 10 React UI component libraries in 2026. Learn their core strengths, use cases, and selection strategies for enterprise, Next.js, and accessible projects. Practical developer guide. As a front-end engineer with 8 years of hands-on React experience, I’ve seen firsthand how the right React UI component libraries 2026 can cut development time by...