Step by step on how to implement Google OAuth in Next.js with NestJS.
Demo example: https://mamolio.vercel.app/signin
Demo code: https://github.com/marwanzaky/mern-ecommerce
1. Create Google OAuth Credentials
- Go to: http://console.cloud.google.com
- Create a project
- Go to: APIs & Services > Credentials > Create credentials > Create OAuth client ID
- Choose:
App type: Web application
Add:
Authorized redirect URI
http://localhost:3001/auth/google/callback
Copy:
CLIENT_ID
CLIENT_SECRET
2. Backend NestJS
Install dependencies:
npm install passport @types/passport @nestjs/passport passport-google-oauth20 @types/passport-google-oauth20
Create Google Strategy
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy, VerifyCallback } from "passport-google-oauth20";
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: `${process.env.SERVER_URL!}/auth/google/callback`,
scope: ["email", "profile"],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
) {
const { emails, name } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
};
done(null, user);
}
}
Auth Controller
@Get('google')
@UseGuards(AuthGuard('google'))
async googleAuth() {
// redirects to Google
}
@Get("google/callback")
@UseGuards(AuthGuard("google"))
async googleAuthRedirect(@Req() req: IRequest, @Res() res: any) {
const clientUrl = process.env.CLIENT_URL!;
const response = await this.authService.loginWithGoogle(req.user as any);
return res.redirect(`${clientUrl}/auth/success?token=${response.token}`);
}
Auth Service
async loginWithGoogle(user: {
email: string;
firstName: string;
lastName: string;
}): Promise<{ token: string }> {
const existingUser = await this.usersService
.findByEmail(user.email);
if (!existingUser) {
const randomPassword = generatePassword();
const newUser = await this.usersService.create({
email: user.email,
name: `${user.firstName ?? ""} ${user.lastName ?? ""}`.trim(),
password: randomPassword,
});
return {
token: await this.createAccessToken(newUser.id, newUser.role)
};
}
return {
token: await this.createAccessToken(existingUser.id, existingUser.role),
};
}
async createAccessToken(userId: string, role: UserRole) {
return await this.jwtService.sign(
{ id: userId, role },
{
secret: this.configService.get("JWT_SECRET"),
expiresIn: this.configService.get("JWT_EXPIRES"),
},
);
}
Environment Variables
CLIENT_URL="http://localhost:3000"
SERVER_URL="http://localhost:3001"
GOOGLE_CLIENT_ID="your_google_client_id"
GOOGLE_CLIENT_SECRET="your_google_client_secret"
JWT_SECRET="your_jwt-secret"
JWT_EXPIRES=30d
3. Frontend (Next.js)
1. app/signin/page.tsx
Redirect user to backend
const handleGoogleLogin = () => {
window.location.href = `${process.env.NEXT_PUBLIC_SERVER!}/auth/google`;
};
button
<button onClick={handleGoogleLogin}>
Continue with Google
</button>
2. app/success/page.tsx
"use client";
import { useRouter, useSearchParams } from "next/navigation";
export default function Page() {
const router = useRouter();
const searchParams = useSearchParams();
useEffect(() => {
const token = searchParams.get('token');
if (token) {
localStorage.setItem('token', token);
router.push('/');
}
}, [searchParams, router]);
return <p>Logging you in...</p>;
}
4. Flow (What Happens)
- User clicks “Continue with Google”
- Redirect → Google login page
- Google redirects back → /auth/google/callback
- NestJS: extracts profile > creates/fetches user > returns JWT
- You store JWT in frontend

Top comments (0)