Open SuzukiTakamasa opened 5 months ago
React/Next.jsとNestJSを用いたGoogle認証機能の実装は、主にGoogle OAuth 2.0を利用することが一般的です。以下に、フロントエンドとバックエンドそれぞれの設定例を複数示します。 1. 基本的なGoogle認証の流れ ユーザーがフロントエンドでGoogleでログインをクリックする。 Googleの認証ページにリダイレクトされる。 認証が完了すると、Googleは指定したリダイレクトURIに認証コードを送信する。 フロントエンドからバックエンドに認証コードを送信する。 バックエンドが認証コードを使ってGoogleのトークンエンドポイントにアクセスし、アクセストークンを取得する。 バックエンドがアクセストークンを使ってGoogle APIにアクセスし、ユーザー情報を取得する。 バックエンドがセッションやJWTを生成し、フロントエンドに返す。 案1: アクセストークンをHTTP Only Cookieに保存する方法 フロントエンド(Next.js) まず、Next.jsでGoogle OAuth用の認証ボタンとAPIリクエストを作成します。 jsx // pages/login.js import { useRouter } from 'next/router'; const Login = () => { const router = useRouter(); const googleLogin = async () => { const response = await fetch('/api/auth/google'); if (response.ok) { router.push('/dashboard'); } }; return ( <div> <button onClick={googleLogin}>Login with Google</button> </div> ); }; export default Login; API RouteでGoogleの認証ページにリダイレクトします。 JavaScript // pages/api/auth/google.js export default (req, res) => { const rootUrl = 'https://accounts.google.com/o/oauth2/auth'; const options = { redirect_uri: 'http://localhost:3000/api/auth/google/callback', client_id: process.env.GOOGLE_CLIENT_ID, access_type: 'offline', response_type: 'code', prompt: 'consent', scope: [ 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', ].join(' '), }; const qs = new URLSearchParams(options); res.redirect(`${rootUrl}?${qs.toString()}`); }; バックエンド(NestJS) NestJSのコントローラを作成し、GoogleのOAuthフローを処理します。 TypeScript // auth.controller.ts import { Controller, Get, Req, Res } from '@nestjs/common'; import { AuthService } from './auth.service'; import { Request, Response } from 'express'; @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} @Get('google/callback') async googleCallback(@Req() req: Request, @Res() res: Response) { const code = req.query.code as string; const jwtToken = await this.authService.getGoogleToken(code); res.cookie('access_token', jwtToken, { httpOnly: true }); return res.redirect('/dashboard'); } } AuthServiceでトークンを取得: TypeScript // auth.service.ts import { Injectable } from '@nestjs/common'; import axios from 'axios'; @Injectable() export class AuthService { async getGoogleToken(code: string): Promise<string> { const { data } = await axios.post('https://oauth2.googleapis.com/token', null, { params: { code, client_id: process.env.GOOGLE_CLIENT_ID, client_secret: process.env.GOOGLE_CLIENT_SECRET, redirect_uri: 'http://localhost:3000/api/auth/google/callback', grant_type: 'authorization_code', }, }); const { id_token } = data; // id_tokenの検証やJWTの生成などを行います。 return id_token; // ここでは簡潔にするためid_tokenをそのまま返しています。 } } 案2: JWTをローカルストレージに保存する方法 フロントエンド jsx // pages/login.js const googleLogin = async () => { const response = await fetch('/api/auth/google'); const data = await response.json(); if (data.url) { window.location.href = data.url; } }; // pages/api/auth/google.js import axios from 'axios'; export default async (req, res) => { const rootUrl = 'https://accounts.google.com/o/oauth2/auth'; const options = { redirect_uri: 'http://localhost:3000/api/auth/google/callback', client_id: process.env.GOOGLE_CLIENT_ID, access_type: 'offline', response_type: 'code', prompt: 'consent', scope: [ 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', ].join(' '), }; const qs = new URLSearchParams(options); res.json({ url: `${rootUrl}?${qs.toString()}` }); }; // pages/api/auth/google/callback.js import axios from 'axios'; export default async (req, res) => { const code = req.query.code as string; const { data } = await axios.post('https://oauth2.googleapis.com/token', null, { params: { code, client_id: process.env.GOOGLE_CLIENT_ID, client_secret: process.env.GOOGLE_CLIENT_SECRET, redirect_uri: 'http://localhost:3000/api/auth/google/callback', grant_type: 'authorization_code', }, }); const { id_token } = data; res.redirect(`/dashboard?token=${id_token}`); }; // pages/dashboard.js import { useEffect } from 'react'; import { useRouter } from 'next/router'; const Dashboard = () => { const router = useRouter(); useEffect(() => { const token = router.query.token; if (token) { localStorage.setItem('access_token', token); } }, [router.query.token]); return <div>Dashboard</div>; }; export default Dashboard; バックエンド NestJSの設定は、上記の案1と同様です。 ミドルウェアの使用 ミドルウェアを使用して認証を行いたい場合、以下のようにJWTを検証するミドルウェアが考えられます。 NestJS ミドルウェア TypeScript // jwt.middleware.ts import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class JwtMiddleware implements NestMiddleware { constructor(private jwtService: JwtService) {} async use(req: Request, res: Response, next: NextFunction) { const token = req.cookies['access_token'] || req.headers['authorization']?.replace('Bearer ', ''); if (!token) { throw new UnauthorizedException(); } try { const user = await this.jwtService.verifyAsync(token); req.user = user; next(); } catch (e) { throw new UnauthorizedException(); } } } // app.module.ts import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { AuthController } from './auth/auth.controller'; import { AuthService } from './auth/auth.service'; import { JwtMiddleware } from './jwt.middleware'; @Module({ imports: [ JwtModule.register({ secret: process.env.JWT_SECRET }), // other modules ], controllers: [AuthController], providers: [AuthService], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(JwtMiddleware).forRoutes('protected-route'); } } このような設定で、各オプションに応じたGoogle認証を簡単に実装することができます。