📑

NestJSでgoogle oauth認証を実装する

2022/12/10に公開約4,700字1件のコメント

準備

新しいnestプロジェクトをnestコマンドで作ってそこで作業します

nest new nest-oauth

できたら、そこに移動して起動するかどうか試してみましょう

cd nest-oauth
npm run start:dev

http://localhost:3000 にアクセスして「Hello World!」と出れば準備OKです

oauth2.0クライアントIDを取得する

GCPのプロジェクトを作り、「認証情報」→「認証情報を作成」→「oauthクライアントID」を選択し、以下の内容で作成します

必要なパッケージのインストール

passportというexpressで有名な認証ライブラリを使います。passportは色々な認証方式(strategy)に対応しています。実装したい認証方式に対応したライブラリをインストールすることで、その認証方式を簡単に実装することができます。今回はgoogle oauthに対応したいので、それ用のライブラリをインストールします。

npm i --save @nestjs/passport passport passport-google-oauth20 @nestjs/config dotenv
npm i -D @types/passport-google-oauth20

nest cliコマンドで必要なファイルを生成

今回はgoogleフォルダを作ってその中にgoogle oauth関連のファイルを詰め込みたいと思います

nest g res google # controllerとserviceとmoduleを作成

passportを使って認証部分を実装する

今回はpassportを用いたgoogle oauthに関する設定ファイルとしてgoogle.strategy.tsを以下のような内容で作成します。

google/google.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { config } from 'dotenv';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';

config(); // .envファイルを使えるようにする

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor() {
    // Passportのstrategyに関する設定
    super({
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: process.env.CALLBACK_URL,
      scope: ['email', 'profile'],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
    done: VerifyCallback,
  ): Promise<any> {
    const { name, emails, photos } = profile;

    const user = {
      email: emails[0].value,
      firstName: name.givenName,
      lastName: name.familyName,
      picture: photos[0].value,
      accessToken,
    };

    done(null, user);
  }
}

.envファイルを作ります

.env
GOOGLE_CLIENT_ID= <your-client-id>
GOOGLE_CLIENT_SECRET= <your-client-secret>
CALLBACK_URL=http://localhost:3000/google/redirect

google.controller.tsで必要なルートを設定します。
AuthGuardをimportして、ハンドラーをguardし、そこにリクエストが行くことで、google oatuh認証フローが開始します。

google/google.controller.ts
import { AuthGuard } from '@nestjs/passport';
import { Controller, Get, Request, UseGuards } from '@nestjs/common';
import { GoogleService } from './google.service';

@Controller('google')
export class GoogleController {
  constructor(private readonly googleService: GoogleService) {}
  
  // ここにリクエストがいくことでoauth認証フローのスタート
  @Get()
  @UseGuards(AuthGuard('google'))
  async googleAuth(@Request() req) {}

  // 認証フローが終了し、アクセストークンを取得したら、ここにリダイレクトされる
  @Get('redirect')
  @UseGuards(AuthGuard('google'))
  googleAuthRedirect(@Request() req) {
    // この時点でreq.userに上のほうで定義したvalidateで抜き出した認証情報が入っている(名前、メールアドレス、画像など)
    // 具体的な処理はserviceにやらせる
    return this.googleService.googleLogin(req);
  }
}

google.service.tsの中を書いていきます。

google/google.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class GoogleService {
  googleLogin(req) {
    if (!req.user) {
      return 'No user from google';
    }

    return {
      message: 'User information from google',
      user: req.user,
    };
  }
}

最後に、DIのためにgoogle.module.tsをごにょごにょして、それをapp.module.tsでimportすればOKです。

google/google.module.ts
import { Module } from '@nestjs/common';
import { GoogleService } from './google.service';
import { GoogleController } from './google.controller';
import { GoogleStrategy } from './google.strategy';

@Module({
  controllers: [GoogleController],
  providers: [GoogleService, GoogleStrategy],
})
export class GoogleModule {}
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GoogleModule } from './google/google.module';

@Module({
  imports: [GoogleModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

テスト

npm run start:dev

この状態で、http://localhost:3000/google にアクセスして、googleアカウントでログイン後、http://localhost:3000/google/redirect にリダイレクトされ、ユーザー情報が見れればOKです。

参考

https://dev.to/chukwutosin_/implement-google-oauth-in-nestjs-using-passport-1j3k

https://knimon-software.github.io/www.passportjs.org/

https://zenn.dev/kisihara_c/books/nest-officialdoc-jp/viewer/security-authentication#passport-jwtの実装

https://docs.nestjs.com/security/authentication

Discussion

userの定義で最終的にユーザーIDを取得していないことが少し気になってしまいました。
メールアドレスをID管理のキーに利用することはアンチパターンです(例えGoogleアカウントのGmailであってもです)。万が一メールアドレスが変わったり新たなメールアドレスが付与された場合に連携がうまくできなくなる可能性があります。

認証用途であれば

  • scope=openid を利用してユーザーIDを取得してその後のID管理に利用する
  • OAuth 2.0 + 認証のための機能を追加した OIDC (OpenID Connect) に対応したライブラリを使う

ということを意識されるとより安全に認証機能を実現できるようになると思います。

ログインするとコメントできます