DEV Community

Marwan Zaky
Marwan Zaky

Posted on

How to Implement Google OAuth 2.0 in Next.js with NestJS

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

  1. Go to: http://console.cloud.google.com
  2. Create a project
  3. Go to: APIs & Services > Credentials > Create credentials > Create OAuth client ID
  4. 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
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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}`);
}
Enter fullscreen mode Exit fullscreen mode

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"),
        },
    );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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`;
};
Enter fullscreen mode Exit fullscreen mode

button

<button onClick={handleGoogleLogin}>
  Continue with Google
</button>
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

4. Flow (What Happens)

  1. User clicks “Continue with Google”
  2. Redirect → Google login page
  3. Google redirects back → /auth/google/callback
  4. NestJS: extracts profile > creates/fetches user > returns JWT
  5. You store JWT in frontend

Top comments (0)