Skip to content

OAuth Authentication in Express

OAuth lets users log in to your app using an existing account from a provider like Google or GitHub, without ever sharing that provider's password with you. This lesson covers the OAuth 2.0 flow and implements it in Express with Passport.

How the OAuth 2.0 Authorization Code Flow Works

Your app redirects the user to the provider (Google), the user logs in and approves access there, and the provider redirects back to your app with an authorization code. Your server exchanges that code for an access token and user profile information, entirely server-to-server, your app never sees the user's Google password.

Passport.js abstracts almost all of this exchange behind a strategy package, passport-google-oauth20 for Google, passport-github2 for GitHub, so implementing it in Express requires configuration far more than manual protocol handling.

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',
  },
  async (accessToken, refreshToken, profile, done) => {
    const user = await findOrCreateUser(profile);
    done(null, user);
  }
));

The strategy's callback receives the provider's profile once Google confirms the user's identity, your job is just to find or create a matching local user record.

OAuth Routes in Express

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'));
  • The first route starts the flow, redirecting the user to Google's consent screen.
  • scope specifies which pieces of profile data you're requesting access to.
  • The callback route is where Google redirects back to after the user approves (or denies) access.
  • failureRedirect sends the user somewhere sensible if they deny access or something goes wrong.

OAuth Cheatsheet

Key terms and steps in a typical OAuth 2.0 login flow.

Term Meaning
Authorization code Short-lived code the provider redirects back with
Access token Token exchanged for the code, used to fetch profile data
Client ID / Secret Credentials identifying your app to the provider
Redirect URI / callback URL Where the provider sends the user back to
Scope Which pieces of data your app is requesting access to

Finding or Creating a Local User

Even though Google verified the user's identity, you still typically maintain your own local user record, linking it to the provider's unique profile ID so returning users are recognized on future logins.

async function findOrCreateUser(profile) {
  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,
    });
  }
  return user;
}

Environment Configuration for OAuth

OAuth requires registering your app with the provider's developer console to obtain a client ID and secret, and configuring the exact redirect URI your app will use, which must match exactly (protocol, domain, and path) between your code and the provider's dashboard.

# .env
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_CALLBACK_URL=https://yourapp.com/auth/google/callback

Common Mistakes

  • Mismatching the redirect URI configured in the provider's dashboard versus the one used in code.
  • Committing OAuth client secrets directly into source control instead of environment variables.
  • Not linking the provider's unique profile ID to a local user, causing duplicate accounts on repeat logins.
  • Requesting broader scope permissions than the app actually needs.

Key Takeaways

  • OAuth 2.0 lets users authenticate via a trusted third party without sharing that provider's password with you.
  • The authorization code flow exchanges a short-lived code for an access token and profile data.
  • Passport strategy packages abstract almost all of the OAuth protocol details.
  • Always link a provider's unique ID to a local user record to correctly recognize returning users.

Pro Tip

Double, then triple, check that the callback URL registered in the provider's developer console exactly matches the one your app uses, mismatched redirect URIs are the single most common OAuth setup failure.