OAuth 2.0 — Complete Guide for Developers
Advertisement
OAuth 2.0 is the industry standard for delegated authentication. Let users login with Google, GitHub, etc.
OAuth 2.0 Flow
Authorization Code flow:
- User clicks "Login with Google"
- App redirects to Google's authorization server
- User approves permissions
- Google redirects back with authorization code
- App exchanges code for access token
- App uses token to get user info
Implementation with Passport.js
npm install passport passport-google-oauth20 express-session
import passport from "passport";
import GoogleStrategy from "passport-google-oauth20";
import session from "express-session";
app.use(
session({
secret: "your-secret",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: "http://localhost:3000/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
let user = await db.users.findOne({ googleId: profile.id });
if (!user) {
user = await db.users.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
});
}
return done(null, user);
}
)
);
passport.serializeUser((user: any, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id: number, done) => {
const user = await db.users.findById(id);
done(null, user);
});
// Routes
app.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile", "email"] })
);
app.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/login" }),
(req, res) => {
res.redirect("/dashboard");
}
);
app.get("/logout", (req, res) => {
req.logout(() => res.redirect("/"));
});
Manual OAuth Implementation
// Redirect to provider
app.get("/login/github", (req, res) => {
const params = new URLSearchParams({
client_id: process.env.GITHUB_CLIENT_ID!,
redirect_uri: "http://localhost:3000/auth/github/callback",
scope: "user:email",
state: generateState(), // CSRF protection
});
res.redirect(`https://github.com/login/oauth/authorize?${params}`);
});
// Handle callback
app.get("/auth/github/callback", async (req, res) => {
const { code, state } = req.query;
if (!validateState(state as string)) {
return res.status(400).json({ error: "Invalid state" });
}
// Exchange code for token
const tokenResponse = await fetch(
"https://github.com/login/oauth/access_token",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code,
}),
}
);
const { access_token } = await tokenResponse.json();
// Get user info
const userResponse = await fetch("https://api.github.com/user", {
headers: { Authorization: `Bearer ${access_token}` },
});
const profile = await userResponse.json();
// Find or create user
let user = await db.users.findOne({ githubId: profile.id });
if (!user) {
user = await db.users.create({
githubId: profile.id,
email: profile.email,
name: profile.name,
});
}
// Generate JWT
const token = jwt.sign({ userId: user.id }, secret, { expiresIn: "24h" });
res.json({ token });
});
- OAuth 2.0 Flow
- Implementation with Passport.js
- Manual OAuth Implementation
- PKCE (for mobile/SPAs)
- Scopes and Permissions
- FAQ
PKCE (for mobile/SPAs)
// Generate code verifier
function generateCodeVerifier() {
const bytes = crypto.randomBytes(32);
return Buffer.from(bytes).toString("base64url");
}
// Generate code challenge
function generateCodeChallenge(verifier: string) {
const hash = crypto.createHash("sha256").update(verifier).digest();
return Buffer.from(hash).toString("base64url");
}
// Use in authorization request
const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);
localStorage.setItem("code_verifier", verifier);
// Exchange code with verifier
await fetch("https://provider.com/token", {
method: "POST",
body: JSON.stringify({
grant_type: "authorization_code",
code,
code_verifier: localStorage.getItem("code_verifier"),
}),
});
Scopes and Permissions
// Request specific scopes
const scopes = ["user:email", "repo", "gist"];
passport.use(
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
scope: scopes,
},
verify
)
);
FAQ
Q: Is OAuth 2.0 secure? A: Yes, if implemented correctly. Always use HTTPS, validate state, protect secrets.
Q: Should I still support passwords? A: Many users prefer OAuth. Support both for flexibility.
Q: What's the difference between OAuth and OpenID Connect? A: OAuth is for authorization. OpenID Connect adds authentication on top.
OAuth 2.0 enables seamless social login experiences. Master it for modern authentication.
Advertisement