Let a Third Party Vouch For Your Users
OAuth — Sign In With Google
OAuth lets users sign in with an existing account at Google, GitHub, or anywhere else. The Passport strategy makes the wiring straightforward.
What you'll learn
- Register an OAuth app with the provider
- Implement a GoogleStrategy
- Wire callback route and issue a session or JWT
OAuth shifts the password problem to someone else’s servers. Your app never sees the user’s credentials — it just trusts the access token that comes back. The pattern is the same for Google, GitHub, Microsoft, and the rest.
Register the OAuth App
Before any code, register your app with the provider (Google Cloud Console, GitHub Developer Settings, etc.). You’ll get:
- A client ID (public)
- A client secret (keep this in env vars)
- A callback URL — the route on your server the provider will redirect to after the user authorizes.
Use https://your-domain.com/auth/google/callback in production and
http://localhost:3000/auth/google/callback in dev.
The Google Strategy
npm i passport-google-oauth20
npm i -D @types/passport-google-oauth20 import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, Profile } from 'passport-google-oauth20';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(cfg: ConfigService) {
super({
clientID: cfg.get<string>('GOOGLE_CLIENT_ID'),
clientSecret: cfg.get<string>('GOOGLE_CLIENT_SECRET'),
callbackURL: cfg.get<string>('GOOGLE_CALLBACK_URL'),
scope: ['profile', 'email'],
});
}
async validate(_accessToken: string, _refreshToken: string, profile: Profile) {
return {
email: profile.emails?.[0]?.value,
name: profile.displayName,
provider: 'google',
providerId: profile.id,
};
}
} The second argument to PassportStrategy(Strategy, 'google') names the
strategy. That’s the name guards will use.
Two Routes: Start and Callback
The first route kicks off the redirect to Google. The second handles the return trip.
@Controller('auth')
export class AuthController {
constructor(private readonly auth: AuthService) {}
@Get('google')
@UseGuards(AuthGuard('google'))
start() { /* AuthGuard redirects, this body never runs */ }
@Get('google/callback')
@UseGuards(AuthGuard('google'))
async callback(@Request() req, @Res({ passthrough: true }) res) {
const user = await this.auth.upsertOAuthUser(req.user);
const token = await this.auth.signJwt(user);
res.cookie('access_token', token, { httpOnly: true, secure: true });
return res.redirect('/');
}
} In the callback, req.user is whatever your validate() returned. The
typical next step is: find or create a local user record, then issue
your JWT — OAuth is for sign-in, not for ongoing API auth.
A Note on Refresh Tokens
If you’ll call Google APIs as the user later (Drive, Calendar), pass
accessType: 'offline' and store the refresh token. If you only need
sign-in, you can throw both tokens away after the callback.