OAuth & Social Login

"Sign in with Google" — Without Storing Passwords

OAuth & Social Login

OAuth lets a third party vouch for the user's identity. Less code, fewer passwords, fewer breach risks.

4 min read Level 3/5 #express#oauth#social-login
What you'll learn
  • Understand the OAuth dance
  • Add Google login via Passport
  • Securely handle the callback

OAuth 2.0 lets users sign in with their existing Google / GitHub / Apple account. Your app never sees their password — the provider vouches for them.

The Flow

1. User clicks "Sign in with Google"
2. Your app redirects to Google with client_id + scope
3. User logs into Google, approves the requested scope
4. Google redirects back to your callback URL with a code
5. Your server exchanges the code for an access token
6. Your server uses the token to fetch the user's info
7. Your server creates or finds a user, issues a session/JWT

Passport handles 2–6 for you.

Register With the Provider

For Google:

  1. Go to https://console.cloud.google.com
  2. Create a project
  3. Configure OAuth consent screen
  4. Create OAuth client ID, type = Web
  5. Add redirect URI: https://yourapp.com/auth/google/callback
  6. Copy the client ID and secret to .env

Wire Up

npm install passport passport-google-oauth20
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";

passport.use(new GoogleStrategy({
  clientID:     process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL:  "/auth/google/callback",
}, async (accessToken, refreshToken, profile, done) => {
  try {
    // 1. Look up by google id
    let user = await db.users.findByGoogleId(profile.id);

    // 2. Or create a new user
    if (!user) {
      const email = profile.emails[0].value;
      user = await db.users.create({
        googleId: profile.id,
        email,
        name: profile.displayName,
      });
    }
    done(null, user);
  } catch (err) {
    done(err);
  }
}));

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");
  }
);

Combining With Local

You can mix: Google sign-in or email/password. Just register both strategies.

For accounts that match by email, decide a policy:

  • Auto-link — if a Google sign-in’s email matches an existing user, link them
  • Manual — show a “merge accounts” prompt

Auto-linking is friendlier; manual is safer (prevents account takeover via email spoofing).

State / PKCE

OAuth has a state parameter to prevent CSRF on the callback. Modern strategies handle it automatically. Same for PKCE — recommended for all OAuth flows now.

Scope Carefully

Only request what you need. email, profile is the minimum. Don’t ask for Drive access if you don’t need it — users distrust apps that overreach.

Hosted Alternative

If you have a choice and time matters: let Clerk / Auth0 do it. The integration is 50 lines, you get every provider for free, plus MFA, password resets, account linking. Implementing all of that yourself is a long road.

Helmet →