Add Google OAuth to a TypeScript application

This note replaces an earlier AI-chat transcript. Its client-side example used gapi.auth2, which is deprecated: Google’s migration guide says the Google Sign-In JavaScript platform library (apis.google.com/js/platform.js, api.js, gapi.auth2) is deprecated and replaced by Google Identity Services (GIS), loaded from https://accounts.google.com/gsi/client.

Choose the flow

NeedUse
”Sign in with Google” (identify the user)GIS google.accounts.id (button, One Tap) in the browser, then verify the ID token on your server
Call Google APIs for the user from a backend (offline access)Server-side authorization-code flow with google-auth-library
Call Google APIs from the browser onlyGIS token client google.accounts.oauth2

Server-side authorization-code flow (Node + TypeScript)

Steps from Google’s web-server OAuth guide: create a client ID and secret in Google Cloud Console, register the redirect URI exactly (HTTPS required except for localhost), redirect the user with response_type=code, scope, a state value against CSRF, and access_type=offline if you need a refresh token; then exchange the returned code for tokens and store them securely.

npm install google-auth-library express  
npm install -D typescript @types/express @types/node  
npx tsc --init  
import express from 'express';  
import { OAuth2Client } from 'google-auth-library';  
  
const app = express();  
const client = new OAuth2Client(  
  process.env.GOOGLE_CLIENT_ID,  
  process.env.GOOGLE_CLIENT_SECRET,  
  process.env.GOOGLE_REDIRECT_URI,  
);  
  
app.get('/auth/google', (_req, res) => {  
  const url = client.generateAuthUrl({  
    access_type: 'offline',  
    scope: ['openid', 'email', 'profile'],  
    state: 'generate-and-store-a-random-value-per-session', // verify on callback  
  });  
  res.redirect(url);  
});  
  
app.get('/auth/google/callback', async (req, res) => {  
  const code = req.query.code;  
  if (typeof code !== 'string') return res.status(400).send('Missing code');  
  const { tokens } = await client.getToken(code);  
  client.setCredentials(tokens);  
  // store tokens server-side; never echo them to the browser  
  res.send('Signed in');  
});  
  
app.listen(process.env.PORT ?? 3000);  

Browser sign-in with GIS, verify on the server

Load the GIS script, call google.accounts.id.initialize({ client_id, callback }) and renderButton; send the returned ID token (credential) to your backend and verify it:

const ticket = await new OAuth2Client().verifyIdToken({  
  idToken: token,  
  audience: process.env.GOOGLE_CLIENT_ID,  
});  
const sub = ticket.getPayload()?.sub; // stable user id  

Google’s docs state that verifyIdToken checks the signature, aud, exp and iss; use the sub claim, not the email, as the user identifier; for Google Workspace accounts you can additionally check the hd (hosted domain) claim. See Google Workspace.

Checklist (general security practice, not all items taken from the cited pages)

  • Keep client ID/secret in environment variables, never in source.
  • Match redirect URIs exactly in the Cloud Console; request minimal scopes.
  • Validate state; use PKCE for public clients.
  • Store refresh tokens encrypted server-side.

Sources

Open items

  • Code samples were adapted from the docs but not executed in this pass.
  • Exact sunset date of gapi.auth2 not stated on the pages fetched.
  • Browser-only GIS token-client example not included.