SuzukiTakamasa / CryptoFunds

0 stars 0 forks source link

ログイン認証について #1

Open SuzukiTakamasa opened 5 months ago

SuzukiTakamasa commented 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認証を簡単に実装することができます。