【ITニュース解説】A Step-by-Step Guide to Implementing Multi-Provider SSO in NestJS with OAuth2
2025年09月10日に「Dev.to」が公開したITニュース「A Step-by-Step Guide to Implementing Multi-Provider SSO in NestJS with OAuth2」について初心者にもわかりやすく解説しています。
ITニュース概要
NestJSで、GoogleやGitHubなど複数のサービスに一つのIDでログインできる機能(SSO)をOAuth2を使い実装する手順を紹介。従来のID/パスワード認証と共存させ、ユーザーのアカウント連携も自動化する。新しいログイン方法の追加も簡単な設計だ。
ITニュース解説
このニュース記事では、NestJSというWebアプリケーションフレームワークを使って、GoogleやGitHub、Microsoftなどの複数の外部サービスでのログインに対応する仕組み、すなわちシングルサインオン(SSO)を実装する方法が解説されている。一般的なWebサービスでは、ユーザー名とパスワードを使った認証がよく使われるが、現代のアプリケーションでは、多くのユーザーが普段使っているGoogleアカウントやGitHubアカウントなどで簡単にログインしたいと考えるため、複数の認証オプションを提供することが求められている。
記事は、すでにNestJSでメールアドレスとパスワードによるJWT認証(JSON Web Tokenを使った認証)が実装されていることを前提として、そのシステムを拡張し、OAuth2という標準的な認証プロトコルを使ってソーシャルログインを追加していく手順を詳しく説明する。この拡張によって、従来のユーザーは引き続きメールとパスワードでログインでき、新しいユーザーはGoogleアカウントなどで簡単に登録・ログインできるようになる。さらに、同じメールアドレスを持つユーザーについては、自動的にアカウントを連携させる機能も備えている。つまり、最初にメールとパスワードで登録したユーザーが、後からGoogleアカウントを紐付けてログインした場合、システムはこれらを同一のユーザーアカウントとして扱う。
具体的に構築するものは、新しいOAuth2サービスを追加しやすくするためのクリーンで拡張性の高い「プロバイダーパターン」という設計、メールアドレスが一致した場合の自動アカウント連携、パスワードなしでOAuthのみでサインアップするユーザーと従来のメール/パスワードユーザーの両方への対応、そしてGoogle、GitHub、Microsoftといった具体的なサービスの統合例だ。
実装に必要なソフトウェア部品(依存関係)としては、まずNode.jsアプリケーションの認証ミドルウェアとして広く使われている「Passport」とその関連ライブラリが挙げられる。これには、メールとパスワード認証用のpassport-local、JWT認証用のpassport-jwt、そしてNestJSでPassportを使いやすくする@nestjs/passportとJWTトークンを扱う@nestjs/jwtが含まれる。パスワードを安全に保存するためにハッシュ化を行うbcryptも必要だ。加えて、OAuth2プロバイダーとしてGoogleアカウントとの連携機能を提供するpassport-google-oauth20とその型定義をインストールする必要がある。
データベースの設計も重要な要素となる。この記事では、データ管理ツールとして「TypeORM」から「Prisma」へ移行している。Prismaは、データベースのスキーマ(設計図)を先に定義することで、開発を簡素化し、OAuthプロバイダーとの連携をスムーズにする利点がある。データベースには、ユーザー情報を保存する「User」テーブルにいくつかの変更を加える。OAuthのみで登録するユーザーのために「password」フィールドをパスワードがなくても登録できるように変更し(null許容)、さらに「isOAuthUser」というフラグを追加して、従来のユーザーかOAuth経由のユーザーかを区別できるようにする。加えて、新しく「OAuthAccount」というエンティティ(テーブル)を追加する。これは、一人のユーザーが複数のOAuthプロバイダー(Google、GitHubなど)を紐付けられるようにするためのもので、「provider」(どのサービスか)と「providerId」(そのサービスにおけるユーザーの固有識別子)を保存する。この設計により、従来のメール/パスワードでの登録、パスワードなしの純粋なOAuthでの登録、そして複数のOAuthアカウントを既存ユーザーに紐付けるアカウント連携の全てに対応できるようになる。
OAuth2のシステム設計の中心は、各プロバイダーに共通の処理を提供する「統一されたアーキテクチャ」の構築だ。まず「OAuthModule」というモジュールを作成する。全てのOAuth2プロバイダーは共通の「OAuthProviderInterface」という型定義に従って実装される。このインターフェースは、ユーザーを認証プロバイダーにリダイレクトするための「authorize()」メソッド、認証後のコールバックを受け取り、ユーザー情報を取得する「callback(code: string)」メソッド、そしてプロバイダー名を返す「getProviderName()」メソッドの三つを定義している。これにより、異なるプロバイダーでも同じ手順で認証フローを処理できるようになる。
次に、「OAuthService」というサービスを作成する。このサービスは、OAuth認証に特化したロジックを担当する。具体的には、OAuthでログイン・登録しようとする新しいユーザーを処理したり、既存のユーザーに新しいOAuthアカウントを連携させたり、メールアドレスを基にした自動アカウント連携を行ったりする。このサービスは、まずOAuthプロバイダーから受け取ったユーザー情報(OAuthUser型)を使って、データベースにそのOAuthアカウントが既に存在するかを確認する。もし存在しなければ、そのユーザーのメールアドレスを使って既存のシステムユーザーを検索する。既存ユーザーがいなければ新しいユーザーを作成し、そのユーザーにOAuthアカウントを紐付けて保存する。
「プロバイダーファクトリー」というパターンもこのアーキテクチャの重要な部分だ。これは「OAuthProviderFactory」というクラスで実現され、複数のOAuth2プロバイダー(例えばGoogleOAuthProviderなど)を登録し、必要に応じてその名前(例:「google」)を指定するだけで、適切なプロバイダーのインスタンスを動的に取得できるようにする。この仕組みのおかげで、新しいOAuthプロバイダーを追加する際に、既存のコードに大きな変更を加える必要がなく、拡張性が非常に高くなる。
ユーザーからのリクエストを受け付ける「OAuthController」は、どんなOAuth2プロバイダーでも機能する汎用的なエンドポイントを提供する。例えば、「/oauth/:provider」というURLにアクセスすると、指定されたプロバイダー(例:google)の認証URLを生成し、ユーザーをそのURLにリダイレクトする。また、「/oauth/:provider/callback」というURLは、OAuthプロバイダーからの認証成功後のコールバックを受け取り、認証処理を完了させる。このコントローラーは、特定のプロバイダーの実装に依存しないため、一度作れば、GoogleでもGitHubでも同じエンドポイントで処理できるという利点がある。
具体的な例として、Google OAuth2の実装が示される。まず、Google Cloud PlatformでOAuthアプリケーションを作成し、クライアントIDとクライアントシークレット、そして認証後にユーザーがリダイレクトされるURL(リダイレクトURI)を設定する。これらの設定情報は環境変数として管理する。その後、「GoogleOAuthProvider」というクラスを作成し、前述の「OAuthProviderInterface」に従って「authorize()」「callback()」「getProviderName()」の各メソッドを実装する。「authorize()」では、設定情報を使ってGoogleの認証URLを構築し、「callback()」では、Googleから受け取った認証コードを使ってアクセストークンを取得し、そのアクセストークンを使ってGoogleのユーザー情報を取得する。最後に、「OAuthProviderFactory」のコンストラクタ内でこの「GoogleOAuthProvider」を登録し、「OAuthModule」に全てのOAuth関連コンポーネントを追加することで、Googleによるシングルサインオンが機能するようになる。
この一連の設計と実装により、非常に拡張性が高く、保守しやすいマルチプロバイダーSSOシステムが実現できる。今後、GitHubやMicrosoftなど他のOAuth2プロバイダーを追加する際も、新たに「OAuthProviderInterface」を実装したクラスを作成し、ファクトリーに登録するだけでよく、既存のコントローラーやサービス、データベーススキーマに大きな変更を加える必要はない。これは、セキュリティ、保守性、そしてユーザーにとっての使いやすさのバランスが取れた認証システムを構築するための強力なアプローチだと言える。