Dev Study
← 解説「認証の流れ — Passportと戦略」に戻る

サンプルコードで身につける: 認証の流れ — Passportと戦略

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1認証の3ステップ

ログインから以降のアクセスまでの大きな流れをコメントで示す例です。「検証 → トークン発行 → 以降はトークンで本人確認」という3ステップが、あらゆる認証方式に共通する骨格です。

// 認証の典型的な流れ
//
// ① ログイン(1回だけ)
//    クライアント ── email + password ──▶ サーバー
//                                         検証して本人と確認
//    クライアント ◀──── トークン ────────  発行して返す
//
// ② 以降のリクエスト(毎回)
//    クライアント ── Authorization: Bearer <token> ──▶ サーバー
//                                         トークンを確認するだけ
//                                         (再ログイン不要)
//
// 認証 = 「あなたは誰か」を確かめる処理

2戦略の差し替えという考え方

Passport が「戦略(Strategy)」単位で認証方式を切り替える設計であることを示す例です。ID/パスワードで確かめる LocalStrategy、トークンで確かめる JwtStrategy のように、同じ口で実装だけを差し替えられます。

// Passport は「戦略」を差し替えて認証方式を選ぶ
//
//   LocalStrategy  … username / password で検証(ログイン時)
//   JwtStrategy    … Authorization ヘッダのトークンで検証(以降)
//   GoogleStrategy … OAuth で外部 ID プロバイダに委ねる
//
// どの戦略も validate() の戻り値が request.user になる、
// という共通の契約だけを守る
//
//   @UseGuards(AuthGuard("local")) → LocalStrategy を使うガード
//   @UseGuards(AuthGuard("jwt"))   → JwtStrategy を使うガード

3LocalStrategy — ログイン時の検証

メールとパスワードで本人確認する LocalStrategy の実装例です。validate() の中で前レッスンの AuthService に照合を任せ、成功して返した値が request.user になる、という戦略の契約を示します。

import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local";

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    // 既定の username の代わりに email フィールドを読む設定
    super({ usernameField: "email" });
  }

  // validate の戻り値が request.user に入る
  async validate(email: string, password: string) {
    const user = await this.authService.validateUser(email, password);
    if (!user) throw new UnauthorizedException(); // 401 になる
    return user;
  }
}

4request.user を取り出す

戦略が検証して返したユーザーを、ハンドラ側で @Request() から受け取る例です。各ハンドラで認証コードを書かずに、ガードを通った後のリクエストには user が入っている、という流れを示します。

import { Controller, Get, Request, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@Controller("profile")
export class ProfileController {
  // jwt 戦略のガードを通過したリクエストだけが到達する
  @UseGuards(AuthGuard("jwt"))
  @Get()
  getProfile(@Request() req: { user: { id: number; email: string } }) {
    // 戦略の validate() が返した値がここに入っている
    return { id: req.user.id, email: req.user.email };
  }
}

5ログインAPI — 検証してトークンを返す

LocalAuthGuard で検証を済ませ、成功したユーザーにトークンを発行して返す実務のログインAPIです。ガードが認証を担うため、ハンドラ本体は「トークンを作って返す」ことに集中できます。

import { Controller, Post, Request, UseGuards, HttpCode } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@Controller("auth")
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  // local 戦略がボディの email/password を検証してから login が動く
  @UseGuards(AuthGuard("local"))
  @Post("login")
  @HttpCode(200) // 「作成」ではないので 200
  async login(@Request() req: { user: { id: number; email: string } }) {
    // ここに来た時点で本人確認済み。トークンを発行して返す
    return this.authService.issueToken(req.user);
  }
}