Step by step on how to implement Google OAuth 2.0 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 > OAuth client ID
- Choose:
App type: Web application
Add:
Authorized redirect URI
http://localhost:3001/auth/google/callback
- Click Create and copy
CLIENT_ID, andCLIENT_SECRET
2. Backend (NestJS)
Install Dependencies
npm install passport @types/passport @nestjs/passport passport-google-oauth20 @types/passport-google-oauth20
Create Google Strategy
// google.strategy.ts
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: {
name: { familyName: string; givenName: string };
emails: { value: string }[];
},
done: VerifyCallback,
) {
const { emails, name } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
};
done(null, user);
}
}
Auth Controller
// auth/auth.controller.ts
@Get('google')
@UseGuards(AuthGuard('google'))
async googleAuth() {
// redirects to Google
}
@Get("google/callback")
@UseGuards(AuthGuard("google"))
async googleAuthRedirect(@Req() req: any, @Res() res: any) {
const response = await this.authService.loginWithGoogle(req.user);
return res.redirect(`${process.env.CLIENT_URL!}/auth/success?token=${response.token}`);
}
Auth Service
// auth/auth.service.ts
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: process.env.JWT_SECRET!,
expiresIn: process.env.JWT_EXPIRES!,
},
);
}
App Module
// app.module.ts
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: ".env",
isGlobal: true,
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
uri: config.get<string>("MONGODB_URI"),
}),
}),
AuthModule,
UsersModule,
],
providers: [
JwtService,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
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)
Signin Page
// app/signin/page.tsx
const handleGoogleLogin = () => {
window.location.href = `${process.env.NEXT_PUBLIC_SERVER!}/auth/google`;
};
<button onClick={handleGoogleLogin}>Continue with Google</button>;
Success Page
// 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>;
}
Environment Variables
NEXT_PUBLIC_SERVER="http://localhost:3001"
3. 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
You are all set to use the Sign in with Google button for the web. You can review my code to implement “Sign In with Google” for Next.js and NestJS on GitHub.


Top comments (0)