Open2

SupabaseとNestJSで認証機能を実装する方法について

まさぴょんまさぴょん

NestJS と Supabaseで認証機能を実装する方法

NestJS は、拡張性と生産性を重視した Node.js のフレームワークです。一方、Supabase はオープンソースの Firebase 代替であり、認証、データベース、ストレージなどのバックエンドサービスを提供します。この2つを組み合わせることで、強力でスケーラブルな認証システムを構築できます。

以下に、NestJS と Supabase を使用して認証機能を実装する手順を詳しく説明します。


前提条件

  • Node.js と npm がインストールされていること
  • TypeScript と NestJS の基本的な知識
  • Supabase アカウント(supabase.io でサインアップできます)

ステップ1: Supabase プロジェクトのセットアップ

  1. Supabase アカウントの作成

    • Supabase の公式サイトでアカウントを作成します。
    • 新しいプロジェクトを作成し、プロジェクトの URL と API キー(anon キーと service_role キー)を取得します。
  2. 認証プロバイダーの設定

    • Supabase ダッシュボードの「Authentication」タブに移動します。
    • 必要な認証プロバイダー(Email、Google、GitHub など)を有効にします。
    • メールテンプレートやリダイレクト URL など、追加の設定を行います。

ステップ2: NestJS プロジェクトのセットアップ

  1. 新しい NestJS アプリケーションの作成

    npm i -g @nestjs/cli
    nest new my-auth-app
    cd my-auth-app
    
  2. Supabase クライアントライブラリのインストール

    npm install @supabase/supabase-js
    

ステップ3: Supabase の設定

  1. Supabase モジュールの作成

    • Supabase に関連する機能をカプセル化するための新しいモジュールを作成します。

      nest g module supabase
      
  2. 環境変数の設定

    • プロジェクトのルートに .env ファイルを作成し、以下を追加します。

      SUPABASE_URL=your_supabase_url
      SUPABASE_ANON_KEY=your_supabase_anon_key
      SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
      
    • dotenv パッケージをインストールして環境変数をロードします。

      npm install dotenv
      
    • main.ts を更新して環境変数をロードします。

      import { NestFactory } from '@nestjs/core';
      import { AppModule } from './app.module';
      import * as dotenv from 'dotenv';
      
      async function bootstrap() {
        dotenv.config();
        const app = await NestFactory.create(AppModule);
        await app.listen(3000);
      }
      bootstrap();
      
  3. Supabase クライアントの初期化

    • SupabaseModule 内で Supabase クライアントを初期化するプロバイダーを作成します。

      // supabase.module.ts
      import { Module, Global } from '@nestjs/common';
      import { createClient, SupabaseClient } from '@supabase/supabase-js';
      
      @Global()
      @Module({
        providers: [
          {
            provide: 'SUPABASE_CLIENT',
            useFactory: (): SupabaseClient => {
              const supabaseUrl = process.env.SUPABASE_URL;
              const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
              return createClient(supabaseUrl, supabaseServiceRoleKey);
            },
          },
        ],
        exports: ['SUPABASE_CLIENT'],
      })
      export class SupabaseModule {}
      

ステップ4: 認証ロジックの実装

  1. Auth サービスの作成

    • 認証ロジックを処理するための新しいサービスを生成します。

      nest g service auth
      
    • Supabase クライアントを AuthService に注入します。

      // auth.service.ts
      import { Injectable, Inject } from '@nestjs/common';
      import { SupabaseClient } from '@supabase/supabase-js';
      
      @Injectable()
      export class AuthService {
        constructor(
          @Inject('SUPABASE_CLIENT') private readonly supabase: SupabaseClient,
        ) {}
      
        async signUp(email: string, password: string) {
          const { user, error } = await this.supabase.auth.signUp({
            email,
            password,
          });
          if (error) {
            throw new Error(error.message);
          }
          return user;
        }
      
        async signIn(email: string, password: string) {
          const { user, error } = await this.supabase.auth.signInWithPassword({
            email,
            password,
          });
          if (error) {
            throw new Error(error.message);
          }
          return user;
        }
      
        // その他のメソッド(signOut、パスワードリセットなど)
      }
      
  2. Auth コントローラーの作成

    • 認証ルートを処理するための新しいコントローラーを生成します。

      nest g controller auth
      
    • auth.controller.ts にルートを実装します。

      // auth.controller.ts
      import { Controller, Post, Body } from '@nestjs/common';
      import { AuthService } from './auth.service';
      
      @Controller('auth')
      export class AuthController {
        constructor(private readonly authService: AuthService) {}
      
        @Post('signup')
        async signUp(
          @Body('email') email: string,
          @Body('password') password: string,
        ) {
          const user = await this.authService.signUp(email, password);
          return { user };
        }
      
        @Post('signin')
        async signIn(
          @Body('email') email: string,
          @Body('password') password: string,
        ) {
          const user = await this.authService.signIn(email, password);
          return { user };
        }
      
        // その他のルート(signOut など)
      }
      
  3. 認証トークンの処理

    • Supabase は JWT トークンを使用して認証します。
    • signIn メソッドのレスポンスから access_tokenrefresh_token を取得できます。
  4. Auth ガードの実装

    • 認証が必要なルートを保護するためのガードを作成します。

      nest g guard auth
      
    • ガードを実装します。

      // auth.guard.ts
      import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
      import { SupabaseClient } from '@supabase/supabase-js';
      import { Inject } from '@nestjs/common';
      
      @Injectable()
      export class AuthGuard implements CanActivate {
        constructor(
          @Inject('SUPABASE_CLIENT') private readonly supabase: SupabaseClient,
        ) {}
      
        async canActivate(context: ExecutionContext): Promise<boolean> {
          const request = context.switchToHttp().getRequest();
          const accessToken = request.headers['authorization']?.split(' ')[1];
      
          if (!accessToken) {
            return false;
          }
      
          const { data: user, error } = await this.supabase.auth.getUser(
            accessToken,
          );
      
          if (error || !user) {
            return false;
          }
      
          request.user = user;
          return true;
        }
      }
      
    • ガードを保護したいルートに適用します。

      // protected.controller.ts
      import { Controller, Get, UseGuards } from '@nestjs/common';
      import { AuthGuard } from './auth.guard';
      
      @Controller('protected')
      @UseGuards(AuthGuard)
      export class ProtectedController {
        @Get()
        getProtectedResource() {
          return { data: 'This is protected data' };
        }
      }
      

ステップ5: 実装のテスト

  1. サーバーの起動

    npm run start
    
  2. API エンドポイントのテスト

    • サインアップ

      POST http://localhost:3000/auth/signup
      Content-Type: application/json
      
      {
        "email": "user@example.com",
        "password": "password123"
      }
      
    • サインイン

      POST http://localhost:3000/auth/signin
      Content-Type: application/json
      
      {
        "email": "user@example.com",
        "password": "password123"
      }
      
    • 保護されたルートへのアクセス

      GET http://localhost:3000/protected
      Authorization: Bearer YOUR_ACCESS_TOKEN
      

ステップ6: 追加の考慮事項

  1. トークンのリフレッシュ

    • refresh_token を使用してトークンのリフレッシュ機能を実装します。
  2. ロールベースのアクセス制御(RBAC)

    • Supabase のポリシーとロールを使用して、ユーザーのアクセスレベルを制御できます。
  3. エラーハンドリング

    • 適切なエラーハンドリングとレスポンスを実装します。
  4. セキュリティ

    • トークンを安全に保存し、プロダクション環境では HTTPS を使用します。

まとめ

Supabase と NestJS を統合することで、効率的に認証機能を実装できます。Supabase がユーザ管理の重荷を軽減し、NestJS が堅牢な API を構築するためのフレームワークを提供します。

参考・引用

https://iamstarcode.hashnode.dev/using-supabase-authentication-in-nestjs

https://github.com/iamstarcode/nestjs-supabase-auth

https://github.com/hiro1107/nestjs-supabase-auth

https://supabase.io/docs

https://docs.nestjs.com

https://github.com/supabase/supabase-js