I’m setting up Google OAuth 2.0 for my app, and I’m encountering an issue where everything works perfectly in production (https://mywebsite.com
), but in development (localhost
), I get an “Error 400: origin_mismatch” error during the Google sign-in process.
Error Message
The error displayed in the Google sign-in modal is:
Access blocked: Authorization Error
[email protected]
You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy.
If you're the app developer, register the JavaScript origin in the Google Cloud Console.
Error 400: origin_mismatch
Setup Details
- Frontend:
localhost:3000
(React) - Backend:
localhost:8000
(Express with Node.js)
Google Cloud Console Configuration
I’ve configured my Google Cloud Console with the following:
-
Authorized JavaScript Origins:
http://localhost:3000
https://mywebsite.com
(for production)
-
Authorized redirect URIs:
http://localhost:8000/api/callback
(for development)https://mywebsite.com/api/callback
(for production)
Relevant Code
Backend OAuth Route (Express.js in auth.js
file):
const express = require("express");
const router = express.Router();
const oauth2Client = require("../config/googleConfig");
const SCOPES = [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email'
];
// Redirect to Google's OAuth 2.0 server to initiate authentication
router.get('/google', (req, res) => {
console.log(`/google called`);
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
redirect_uri: 'http://localhost:8000/api/callback' // Explicitly set redirect URI for development
});
res.redirect(authUrl);
});
// Handle the OAuth 2.0 server response
router.get('/callback', async (req, res) => {
console.log(`/callback req.query: `, req.query);
const { code } = req.query;
try {
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials(tokens);
req.session.tokens = tokens;
res.redirect('http://localhost:3000'); // Redirect back to frontend after successful login
} catch (error) {
console.error('Error during OAuth callback:', error.response ? error.response.data : error);
res.status(500).send('Authentication failed');
}
});
module.exports = router;
CORS Configuration (Express middleware in server.js
):
// process.env.CLIENT_URL === http://localhost:3000
app.use(cors({
origin: `${process.env.CLIENT_URL}`,
credentials: true
}));
Session Configuration (Express express-session
setup):
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.DATABASE }),
cookie: {
maxAge: 2 * 60 * 60 * 1000, // 2 hours
httpOnly: true,
secure: false, // Set to false in development
sameSite: 'Lax' // Ensures cookies are accessible across localhost:3000 and localhost:8000
}
}));
And now for the frontend code that is used to communicate with the backend.
Frontend React API Call function
export const googleSignIn = async (token) => { // not the JWT token, the google token
try {
const response = await fetch(`${API}/google-login`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include', // Ensure session cookie is included
body: JSON.stringify({ idToken: token }),
});
return await response.json();
} catch (err) {
return { error: 'Google sign-in failed. Please try again.' };
}
};
What I’ve Tried
-
Verified Authorized JavaScript Origins and Redirect URIs: Double-checked that
http://localhost:3000
is set as an authorized JavaScript origin andhttp://localhost:8000/api/callback
as an authorized redirect URI in the Google Cloud Console for development. -
Explicitly Set Redirect URI in
generateAuthUrl
: Ensured that theredirect_uri
is specified directly in thegenerateAuthUrl
call to match what’s in the Google Console. -
Adjusted SCOPES: Initially, I used
'https://www.googleapis.com/auth/drive.file'
, but I changed it to:const SCOPES = [ 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email' ];
This change was made to avoid potential scope conflicts, as I only need basic user info for authentication.
-
Cleared Cache and Cookies: Cleared browser cache and cookies on
localhost
, and also tested in incognito mode to eliminate any caching issues. -
Console Logs and Network Activity: Inspected network logs in the browser developer tools. I verified that the
authUrl
generated in the/google
route matches the redirect URI set in the Google Console.
Observed Behavior
- Production: Works without issues on
https://mywebsite.com
, with users able to log in and authenticate. - Development: Returns “origin_mismatch” error during the last step of the Google OAuth process in the sign-in modal.
Question
Why is Google OAuth throwing an “origin_mismatch” error only in the development environment? Are there specific configurations for localhost that I might be overlooking? Any advice on how to fix this issue would be greatly appreciated!