Implementing Google Login β Passport + OAuth
After completing this topic
You will be able to configure an OAuth client in the Google Cloud Console and implement social login using Passport.js's Google Strategy.
Prerequisites
You need to understand the following concepts from previous topics:
- OAuth 2.0 Flow β The four players in the Authorization Code Flow
- Passport.js β The Strategy pattern and serializeUser/deserializeUser
- Express Session β Session-based authentication
These three concepts work together to enable Google login.
Step 1 β Google Cloud Console Setup
You need to register our app with Google. This process involves web console tasks rather than code.
- Google Cloud Console β Create a new project
- "OAuth consent screen" β Enter app name and email
- "Credentials" β Create OAuth 2.0 client ID
- Add
http://localhost:3000/auth/google/callbackto the authorized redirect URIs.
As a result, you will receive two values: Client ID and Client Secret. Store these values in your .env file.
GOOGLE_CLIENT_ID=abc123...
GOOGLE_CLIENT_SECRET=xyz789...Step 2 β Package Installation
npm install passport-google-oauth20Assume that passport and express-session are already installed.
Step 3 β Register the Strategy
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback',
},
(accessToken, refreshToken, profile, done) => {
// The Google user information is contained in profile.
// Find or create the user in the database.
const user = {
googleId: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
photo: profile.photos[0].value,
};
// In reality, perform a database query/save operation.
done(null, user);
}
));This callback function is called after Google completes the authorization code-to-token exchange. Passport handles all the complexities of the OAuth flow, and we just receive the user information.
Step 4 β Connect the Routes
// Start Google login
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
// Google sends the authorization code here
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/');
}
);
// Logout
app.get('/logout', (req, res) => {
req.logout(() => {
res.redirect('/');
});
});Let's follow the flow:
- The user accesses
/auth/googleβ Redirected to the Google login screen. - The user logs in to Google and grants permissions β Google redirects to
/auth/google/callback?code=xxx. - Passport exchanges the
codefor a token β The Strategy callback is invoked β The user is stored in the session. res.redirect('/')β Redirected to the home page with the user logged in.
Step 5 β Serialize/Deserialize
passport.serializeUser((user, done) => {
done(null, user.googleId);
});
passport.deserializeUser((googleId, done) => {
// In reality, query the database for the user by googleId.
done(null, { googleId, name: '...' });
});serialize defines what to store in the session, and deserialize defines how to restore the user object from the value retrieved from the session.
Instead of storing the entire user object in the session, store only the ID. Reducing the size of the session data reduces the server load.
Overall Structure
User Our Server Google
β β β
ββ /auth/google βββββββ β
β ββ redirect ββββββββββ
β β β
ββββ Google login screen ββ€ β
β β β
ββ Login+Permission βββββββββ β
β ββββ code βββββββββββ€
β ββ code+secret βββββββ
β ββββ access_token βββ€
β ββ token βββββββββββββ
β ββββ profile ββββββββ€
ββββ Session created, / ββββββ€ βPassport handles the middle five steps (receiving the code β exchanging it for a token β requesting the profile) in a single Strategy. All we have to do is register the Strategy and connect the routes.
Common Mistakes
- Redirect URI mismatch β The URI registered in the console must be exactly the same as the
callbackURLin the code. An error can occur even if there is only one slash difference. - Missing scope β If you don't include
scope: ['profile', 'email'],profile.emailswill be undefined. - Client Secret exposure β Store it in
.envand add.envto.gitignore. If the Secret is uploaded to GitHub, Google will automatically disable it.
Key Takeaway
Passport.js + Google Strategy wraps all the complexities of the OAuth 2.0 flow into a single Strategy. All we have to do is Console setup β Strategy registration β Connect 2 routes. Key flow:
/auth/googleβ Google login β/auth/google/callbackβ Session creation β Redirect to home.