Google OAuth
The Google OAuth component provides a secure and standardized way to integrate Google authentication into your ServerCN Express applications using the official google-auth-library.
It handles the complete OAuth 2.0 flow including authorization URL generation, token exchange, user information retrieval, and token refresh.
Features
- Complete OAuth 2.0 flow - Authorization, token exchange, and user info retrieval
- Secure by default - CSRF protection with state parameter
- Token management - Access token, refresh token, and ID token verification
- Express integration - Ready-to-use route handlers
- Type-safe - Full TypeScript support
- Flexible scopes - Customizable OAuth scopes
Installation Guide
This component requires additional ServerCN components.
👉 Note: You do not need to install any servercn dependencies manually. Installing this component will automatically install all required servercn dependencies. Manual installation is optional if you prefer to manage dependencies yourself.
1. Install ServerCN dependencies(Optional)
- HTTP Status Codes:
npx servercn add http-status-codesDocumentation: HTTP Status Codes
- Api Response Handler:
npx servercn add response-formatterDocumentation: Api Response Handler
- Api Error Handler:
npx servercn add error-handlerDocumentation: Api Error Handler
- Async Handler:
npx servercn add async-handlerDocumentation: Async Handler
3. Install this component
npx servercn add google-oauthPrerequisites
Google Cloud Console Setup
- Go to the Google Cloud Console
- Create a new project or select an existing one
- Enable the Google+ API (or Google Identity API)
- Go to Credentials → Create Credentials → OAuth client ID
- Configure the OAuth consent screen:
- Choose External (for testing) or Internal (for Google Workspace)
- Fill in the required information
- Create OAuth 2.0 Client ID:
- Application type: Web application
- Authorized redirect URIs: Add your callback URL (e.g.,
http://localhost:8000/api/auth/google/callback)
- Copy the Client ID and Client Secret
Environment Variables
Add the following to your .env file:
GOOGLE_CLIENT_ID="your-google-client-id.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
GOOGLE_REDIRECT_URI="http://localhost:8000/api/auth/google/callback"Ensure the following configuration are defined:
src/configs/env.ts ||
src/shared/configs/env.ts
interface Config {
PORT: number;
NODE_ENV: string;
LOG_LEVEL: string;
GOOGLE_CLIENT_ID: string;
GOOGLE_CLIENT_SECRET: string;
GOOGLE_REDIRECT_URI: string;
}
const env: Config = {
PORT: Number(process.env.PORT) || 3000,
NODE_ENV: process.env.NODE_ENV || "development",
LOG_LEVEL: process.env.LOG_LEVEL || "info",
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID!,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET!,
GOOGLE_REDIRECT_URI: process.env.GOOGLE_REDIRECT_URI!
};
export default env;Basic Implementation
MVC Structure
Place the Google OAuth utilities in src/utils/google-oauth.ts:
import { OAuth2Client } from "google-auth-library";
import env from "../configs/env";
const oauth2Client = new OAuth2Client(
env.GOOGLE_CLIENT_ID,
env.GOOGLE_CLIENT_SECRET,
env.GOOGLE_REDIRECT_URI
);
export function getGoogleAuthUrl(
scopes: string[] = [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
) {
const state = Math.random().toString(36).substring(2, 15);
const authUrl = oauth2Client.generateAuthUrl({
access_type: "offline",
scope: scopes,
state,
prompt: "consent"
});
return { authUrl, state };
}
export async function getGoogleTokens(code: string) {
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials(tokens);
return tokens;
}
export async function getGoogleUserInfo(accessToken: string) {
const userInfoResponse = await fetch(
"https://www.googleapis.com/oauth2/v2/userinfo",
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
if (!userInfoResponse.ok) {
throw new Error("Failed to fetch user info from Google");
}
return userInfoResponse.json();
}Feature-Based Structure
For feature-based architecture, place helpers in src/modules/auth/google-oauth.helpers.ts:
import { OAuth2Client } from "google-auth-library";
const oauth2Client = new OAuth2Client(
process.env.GOOGLE_CLIENT_ID!,
process.env.GOOGLE_CLIENT_SECRET!,
process.env.GOOGLE_REDIRECT_URI!
);
// Same functions as MVC structureUsage Examples
1. Initiate OAuth Flow
Create a route to start the Google OAuth flow:
import { Router } from "express";
import { getGoogleAuthUrl } from "../utils/google-oauth";
const router = Router();
router.get("/auth/google", async (req, res) => {
const { authUrl, state } = getGoogleAuthUrl();
// Store state in session/Redis for CSRF protection
req.session.oauthState = state;
// Redirect user to Google
res.redirect(authUrl);
});2. Handle OAuth Callback
Handle the callback from Google:
router.get("/auth/google/callback", async (req, res) => {
const { code, state } = req.query;
// Verify state to prevent CSRF
if (state !== req.session.oauthState) {
return res.status(400).json({ error: "Invalid state parameter" });
}
try {
// Exchange code for tokens
const tokens = await getGoogleTokens(code as string);
// Get user information
const userInfo = await getGoogleUserInfo(tokens.access_token!);
// Create or update user in your database
// Generate your own JWT tokens
// Return tokens to client
res.json({ user: userInfo, tokens });
} catch (error) {
res.status(500).json({ error: "Authentication failed" });
}
});3. Verify ID Token
If using Google Sign-In with ID tokens:
import { verifyGoogleIdToken } from "../utils/google-oauth";
router.post("/auth/google/verify", async (req, res) => {
const { idToken } = req.body;
try {
const payload = await verifyGoogleIdToken(idToken);
// payload contains user information
res.json({ user: payload });
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
});4. Refresh Access Token
Refresh an expired access token:
import { refreshGoogleToken } from "../utils/google-oauth";
router.post("/auth/google/refresh", async (req, res) => {
const { refreshToken } = req.body;
try {
const credentials = await refreshGoogleToken(refreshToken);
res.json({ tokens: credentials });
} catch (error) {
res.status(401).json({ error: "Token refresh failed" });
}
});Integration with JWT Authentication
Combine Google OAuth with your JWT authentication:
import { getGoogleTokens, getGoogleUserInfo } from "../utils/google-oauth";
import { generateAccessToken, generateRefreshToken } from "../utils/jwt";
router.get("/auth/google/callback", async (req, res) => {
const { code } = req.query;
try {
// Get Google tokens
const googleTokens = await getGoogleTokens(code as string);
const userInfo = await getGoogleUserInfo(googleTokens.access_token!);
// Find or create user in database
let user = await User.findOne({ email: userInfo.email });
if (!user) {
user = await User.create({
email: userInfo.email,
name: userInfo.name,
picture: userInfo.picture,
provider: "google",
providerId: userInfo.id
});
}
// Generate your own JWT tokens
const accessToken = generateAccessToken({ _id: user._id });
const refreshToken = generateRefreshToken(user._id);
res.json({
accessToken,
refreshToken,
user: {
id: user._id,
email: user.email,
name: user.name
}
});
} catch (error) {
res.status(500).json({ error: "Authentication failed" });
}
});Available OAuth Scopes
Common Google OAuth scopes you can request:
https://www.googleapis.com/auth/userinfo.email- User's email addresshttps://www.googleapis.com/auth/userinfo.profile- User's basic profile infohttps://www.googleapis.com/auth/user.birthday.read- User's birthdayhttps://www.googleapis.com/auth/user.phonenumbers.read- User's phone numbershttps://www.googleapis.com/auth/calendar.readonly- Read-only access to user's calendar
Security Best Practices
- Always verify the state parameter to prevent CSRF attacks
- Store refresh tokens securely in your database (encrypted)
- Use HTTPS in production for redirect URIs
- Validate redirect URIs match your configured ones
- Set appropriate token expiration times
- Handle token refresh automatically when access tokens expire
Error Handling
The component throws errors that can be caught and handled:
try {
const tokens = await getGoogleTokens(code);
} catch (error) {
if (error instanceof Error) {
// Handle specific Google OAuth errors
console.error("OAuth error:", error.message);
}
}Common Issues
"redirect_uri_mismatch"
Ensure your redirect URI in .env exactly matches the one configured in Google Cloud Console.
"invalid_grant"
This usually means:
- The authorization code has expired (codes expire after a few minutes)
- The code has already been used
- The redirect URI doesn't match
"access_denied"
The user denied permission. Handle this gracefully in your UI.