Skip to main content

Google OAuth

Add Google Sign-In to your app. By the end of this guide you will have these endpoints working:

EndpointMethodAuthPurpose
/auth/social/google/redirectGETPublicStart OAuth flow (redirects to Google)
/auth/social/google/callbackGETPublicGoogle callback (exchanges code for tokens)
/auth/social/exchangePOSTPublicExchange exchangeToken for tokens or challenge
/auth/social/google/verifyPOSTPublicVerify native mobile ID token

The redirect, callback, and exchange endpoints use the shared social routes you set up in the How Social Login Works guide. This page adds the Google-specific configuration and the native mobile verify endpoint.

Sample apps

Google social login is fully implemented in the nauth example apps — see the NestJS, Express, and Fastify examples for social routes, and the React/Angular examples for the login button and callback handling.

Prerequisites

Step 1: Get Google Credentials

  1. Open the Google Cloud Console
  2. Create or select a project
  3. Click Create Credentials > OAuth client ID
  4. Application type: Web application
  5. Add Authorized redirect URIs:
    • Development: http://localhost:3000/auth/social/google/callback
    • Production: https://api.example.com/auth/social/google/callback
  6. Copy the Client ID and Client Secret

For native mobile apps, create additional OAuth client IDs:

  • iOS: Application type "iOS", enter your bundle ID
  • Android: Application type "Android", enter your package name and SHA-1 fingerprint

Step 2: Install

npm install @nauth-toolkit/social-google

Step 3: Configure

config/auth.config.ts
social: {
google: {
enabled: true,
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackUrl: `${process.env.API_BASE_URL}/auth/social/google/callback`,
scopes: ['openid', 'email', 'profile'],
autoLink: true,
allowSignup: true,
},
},

If your app runs on multiple platforms (web + iOS + Android), pass an array of client IDs. The token verifier accepts any of them:

config/auth.config.ts
social: {
google: {
clientId: [
process.env.GOOGLE_CLIENT_ID, // Web
process.env.GOOGLE_IOS_CLIENT_ID, // iOS
process.env.GOOGLE_ANDROID_CLIENT_ID, // Android
],
// ...rest of config
},
},

Step 4: Add the Verify Route

This endpoint verifies native Google ID tokens from Capacitor, React Native, or Flutter apps. Skip this step if you only need web OAuth.

src/auth/social-redirect.controller.ts
import { Inject, BadRequestException, Optional } from '@nestjs/common';
import { VerifyTokenDTO, AuthResponseDTO } from '@nauth-toolkit/nestjs';
import { GoogleSocialAuthService } from '@nauth-toolkit/social-google/nestjs';

// Add to your SocialRedirectController:
constructor(
private readonly socialRedirect: SocialRedirectHandler,
@Optional() @Inject(GoogleSocialAuthService)
private readonly googleAuth?: GoogleSocialAuthService,
) {}

@Public()
@Post('google/verify')
async verifyGoogle(@Body() dto: VerifyTokenDTO): Promise<AuthResponseDTO> {
if (!this.googleAuth) throw new BadRequestException('Google OAuth is not configured');
return await this.googleAuth.verifyToken(dto);
}

Step 5: Frontend

Trigger the login

// Using the frontend SDK:
await client.loginWithSocial('google', {
returnTo: `${window.location.origin}/auth/callback`,
oauthParams: { prompt: 'select_account' },
});

This navigates the browser to GET /auth/social/google/redirect?returnTo=/auth/callback. The backend redirects to Google's consent screen.

Handle the callback

After the user authenticates with Google, the backend redirects to your returnTo URL. Your callback page handles the rest — exchanging the token if needed and navigating the user.

Web Flow: Request and Response Reference

1. Start redirect

GET /auth/social/google/redirect?returnTo=/auth/callback&appState=invite-123

Backend responds with 302 Redirect to Google's OAuth consent screen.

2. Google callback

Google redirects to:

GET /auth/social/google/callback?code=4/0AX4XfW...&state=csrf-token-here

The backend exchanges the code for a Google access token, fetches the user profile, creates or links the account, and redirects to your frontend:

  • Cookies mode (no challenge): https://app.example.com/auth/callback?appState=invite-123
  • JSON/hybrid mode or challenge pending: https://app.example.com/auth/callback?exchangeToken=eyJ...&appState=invite-123

3. Exchange (JSON/hybrid mode)

Request body (SocialExchangeDTO):

{
"exchangeToken": "eyJhbGciOiJIUzI1NiJ9..."
}

Response — tokens + user:

{
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
"accessTokenExpiresAt": 1700000000,
"refreshTokenExpiresAt": 1700600000,
"user": {
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "user@gmail.com",
"firstName": "John",
"lastName": "Doe",
"isEmailVerified": true
}
}

If MFA is configured, the exchange may return a challenge instead:

{
"challengeName": "MFA_REQUIRED",
"session": "a21b654c-2746-4168-acee-c175083a65cd",
"challengeParameters": {
"availableMethods": ["totp"],
"preferredMethod": "totp"
}
}

Complete the challenge via /auth/respond-challenge as described in How Social Login Works > Challenges.

Native Mobile: Request and Response Reference

For Capacitor/React Native apps using the Google Sign-In SDK:

Request body (VerifyTokenDTO):

{
"provider": "google",
"idToken": "eyJhbGciOiJSUzI1NiIs...",
"accessToken": "ya29.a0AfH6SM..."
}

Response — same structure as web flow (tokens + user, or challenge if MFA is required).

The backend verifies the ID token's JWT signature against Google's JWKS keys, validates the aud claim against your configured client IDs, and checks that the email is verified. 5-minute clock tolerance handles minor device/server time differences.

OAuth Parameters

Customize Google's consent screen behavior via config defaults or per-request overrides:

ParameterValuesEffect
promptselect_account, consent, noneForce account chooser, re-consent, or silent auth
hdDomain string (e.g., company.com)Restrict to a Google Workspace domain
login_hintEmail addressPre-fill the email field
include_granted_scopestrueEnable incremental authorization

Set defaults in config:

config/auth.config.ts
social: {
google: {
// ...credentials...
oauthParams: {
prompt: 'select_account',
hd: 'company.com',
},
},
},

Override per-request from the frontend:

await client.loginWithSocial('google', {
returnTo: '/dashboard',
oauthParams: { prompt: 'consent', hd: 'company.com' },
});
Hosted domain filtering

The hd parameter only filters the account chooser UI — it does not enforce domain restriction server-side. If you need strict enforcement, validate the user's email domain in a post-signup hook.

What's Next