Node.js OAuth 2.0 — Complete Guide to Passport.js and Authentication Strategies
In this tutorial, you will learn about Node.js OAuth 2.0. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js OAuth 2.0 with Passport.js enables social login and delegated authorization, supporting Google, GitHub, Facebook, and custom OAuth providers with JWT token issuance.
What You'll Learn
By the end of this tutorial, you'll implement OAuth 2.0 with Passport.js, configure Google and GitHub strategies, issue JWTs after social login, handle token storage, and secure routes.
Why OAuth Matters
OAuth 2.0 eliminates password management for your application. Users authenticate through trusted providers, reducing security risk and improving user experience.
Real-World Use
A SaaS application offers "Login with Google" and "Login with GitHub". Passport.js handles the OAuth flow, creates local user records on first login, and issues JWTs for API access.
OAuth Path
flowchart LR
A[JWT] --> B[OAuth]
B --> C[Security]
C --> D[Deployment]
D --> E[Scaling]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Passport.js Setup
Install and configure Passport.js with Express for OAuth authentication.
const passport = require("passport");
const express = require("express");
const session = require("express-session");
const app = express();
app.use(session({ secret: "session-secret", resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser((id, done) => {
User.findById(id).then((user) => done(null, user));
});
Google OAuth Strategy
Configure Passport with Google OAuth 2.0 credentials.
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",
scope: ["profile", "email"],
}, async (accessToken, refreshToken, profile, done) => {
try {
let user = await User.findOne({ googleId: profile.id });
if (!user) {
user = await User.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value,
});
}
done(null, user);
} catch (err) {
done(err, null);
}
}));
OAuth Routes
Define routes for initiating OAuth and handling the callback.
const express = require("express");
const passport = require("passport");
const jwt = require("jsonwebtoken");
const router = express.Router();
router.get("/auth/google", passport.authenticate("google", { scope: ["profile", "email"] }));
router.get("/auth/google/callback", passport.authenticate("google", {
failureRedirect: "/login",
session: false,
}), (req, res) => {
const token = jwt.sign({ userId: req.user.id, email: req.user.email }, process.env.JWT_SECRET, { expiresIn: "7d" });
res.redirect(`https://app.example.com/login?token=${token}`);
});
module.exports = router;
GitHub OAuth Strategy
Configure GitHub OAuth with additional scope for user data.
const GitHubStrategy = require("passport-github2").Strategy;
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: "/auth/github/callback",
scope: ["user:email"],
}, async (accessToken, refreshToken, profile, done) => {
try {
const email = profile.emails?.[0]?.value || `${profile.username}@github.com`;
let user = await User.findOne({ githubId: profile.id });
if (!user) {
user = await User.create({
githubId: profile.id,
email,
name: profile.displayName || profile.username,
});
}
done(null, user);
} catch (err) {
done(err, null);
}
}));
JWT Issuance After OAuth
Issue JWTs after successful OAuth instead of using sessions for API authentication.
const jwt = require("jsonwebtoken");
const passport = require("passport");
function oauthCallback(strategy) {
return (req, res, next) => {
passport.authenticate(strategy, { session: false }, (err, user) => {
if (err || !user) {
return res.status(401).json({ error: "Authentication failed" });
}
const token = jwt.sign(
{ userId: user.id, email: user.email, provider: strategy },
process.env.JWT_SECRET,
{ expiresIn: "7d" }
);
res.json({ token, user: { id: user.id, name: user.name, email: user.email } });
})(req, res, next);
};
}
router.get("/auth/google/callback", oauthCallback("google"));
Common Mistakes
1. Not Validating OAuth State Parameter
Without state validation, OAuth flows are vulnerable to CSRF Attacks. Passport validates state automatically when enabled.
2. Storing OAuth Tokens Insecurely
Access and refresh tokens from providers grant access to user data. Encrypt them in the database.
3. Not Handling OAuth Account Linking
Users may have accounts from different providers. Implement account linking with email matching.
4. Hardcoding Redirect URIs
OAuth providers require exact URI matching. Use environment variables for callback URLs.
5. Ignoring Token Expiration from Providers
Provider access tokens expire. Check token expiry and refresh before making API calls on behalf of users.
Practice Questions
1. What is the OAuth 2.0 authorization code flow?
The user authorizes your app, the provider sends a code to your callback, and you exchange the code for tokens.
2. What does Passport.js serializeUser do?
Determines what user data is stored in the session. Typically just the user ID.
3. How do you protect routes after OAuth login?
Use JWT middleware that verifies the token issued after OAuth success.
4. What is the purpose of the OAuth state parameter?
Prevents CSRF attacks by ensuring the callback corresponds to the original authorization request.
5. Challenge: Implement a hybrid auth system supporting both Google and GitHub OAuth.
const strategies = ["google", "github"];
strategies.forEach((name) => {
passport.use(name, new Strategy(config[name], async (token, rt, profile, done) => {
const user = await User.findOneAndUpdate(
{ [`${name}Id`]: profile.id },
{ $setOnInsert: { email: profile.emails?.[0]?.value, name: profile.displayName } },
{ upsert: true, new: true }
);
done(null, user);
}));
});
FAQ
Mini Project: Multi-Provider OAuth Server
Build an OAuth server that supports Google and GitHub login with JWT issuance.
const express = require("express");
const passport = require("passport");
const jwt = require("jsonwebtoken");
const app = express();
app.use(passport.initialize());
const providers = {
google: { Strategy: GoogleStrategy, config: { clientID: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET, callbackURL: "/auth/google/callback" }},
github: { Strategy: GitHubStrategy, config: { clientID: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, callbackURL: "/auth/github/callback" }},
};
Object.entries(providers).forEach(([name, { Strategy, config }]) => {
passport.use(name, new Strategy(config, async (at, rt, profile, done) => {
done(null, { id: profile.id, name: profile.displayName, provider: name });
}));
app.get(`/auth/${name}`, passport.authenticate(name));
app.get(`/auth/${name}/callback`, passport.authenticate(name, { session: false }), (req, res) => {
const token = jwt.sign(req.user, process.env.JWT_SECRET, { expiresIn: "7d" });
res.json({ token, user: req.user });
});
});
What's Next
Node.js JWT Authentication Node.js Security Checklist Node.js GraphQL
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro