OAuth & Social Login

Google and GitHub Login via Passport OAuth Strategies

OAuth & Social Login

Add "Sign in with Google" or "Sign in with GitHub" to a Koa app using passport OAuth strategies, callback routes, and session-backed profile storage.

4 min read Level 3/5 #koa#oauth#social-login
What you'll learn
  • Register a Passport OAuth strategy with client credentials
  • Implement the redirect and callback routes for the OAuth flow
  • Upsert the provider profile into your database and store the user in session

OAuth 2.0 lets users authenticate via a trusted third party (Google, GitHub, etc.) without giving your app their password. Passport strategies handle the redirect, token exchange, and profile fetch; you only need to store the result.

Installation

npm install passport-google-oauth20 passport-github2

Ensure koa-passport and koa-session are already configured as shown in the previous lesson.

Google Strategy

import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { upsertUser } from "./db.js";

passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: "https://yourdomain.com/auth/google/callback",
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        // Store only what you need; never store the raw accessToken long-term.
        const user = await upsertUser({
          provider: "google",
          providerId: profile.id,
          email: profile.emails?.[0]?.value,
          displayName: profile.displayName,
        });
        done(null, user);
      } catch (err) {
        done(err);
      }
    }
  )
);

GitHub Strategy

import { Strategy as GitHubStrategy } from "passport-github2";

passport.use(
  new GitHubStrategy(
    {
      clientID: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
      callbackURL: "https://yourdomain.com/auth/github/callback",
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        const user = await upsertUser({
          provider: "github",
          providerId: String(profile.id),
          email: profile.emails?.[0]?.value,
          displayName: profile.displayName ?? profile.username,
        });
        done(null, user);
      } catch (err) {
        done(err);
      }
    }
  )
);

Callback Routes

const router = new Router();

// Step 1: redirect the user to the provider.
router.get(
  "/auth/google",
  passport.authenticate("google", { scope: ["profile", "email"] })
);

// Step 2: provider redirects back; Passport handles the token exchange.
router.get(
  "/auth/google/callback",
  passport.authenticate("google", { failureRedirect: "/login" }),
  async (ctx) => {
    ctx.redirect("/dashboard"); // success
  }
);

// GitHub — same pattern.
router.get("/auth/github", passport.authenticate("github", { scope: ["user:email"] }));
router.get(
  "/auth/github/callback",
  passport.authenticate("github", { failureRedirect: "/login" }),
  async (ctx) => {
    ctx.redirect("/dashboard");
  }
);

What to Store

FieldStore?Notes
Provider + provider IDYesNeeded to look up the user on return
EmailYesFor account linking and notifications
Display name / avatarYesUX only — can be re-fetched
Access tokenAvoidOnly if you call provider APIs on behalf of the user
Refresh tokenAvoid unless neededEncrypt at rest if stored

Up Next

Secure your Koa app’s HTTP responses with koa-helmet to add critical security headers in a single middleware call.

Security Headers with koa-helmet →