awesome-hacks
Docs

NestJS SAML認証 / JWT Cookie連携

Passport-SAMLでSAML認証を実装し、JWTをHttpOnly Cookieに保存してSPAと連携する

最終更新:2026/05/27

NestJS Guard / JWT認証まで終えている前提。
この記事では、企業や学校のシングルサインオン(SSO)でよく使われる SAML認証 をNestJSで実装する。認証後はJWTをHttpOnly Cookieに保存し、SPAからのAPIアクセスに活用するパターンを構築する。

認証フロー

sequenceDiagram
  participant B as ブラウザ
  participant SP as NestJS (SP)
  participant IdP as IdP

  B->>SP: GET /auth/saml/login
  note over SP: SamlAuthGuard起動<br>SamlStrategyがSAMLリクエスト生成
  SP->>B: 302リダイレクト(IdPへ)
  B->>IdP: SAMLリクエスト送信
  IdP->>B: ログイン画面を表示
  B->>IdP: ユーザー名・パスワードを入力
  IdP->>B: SAMLアサーション(HTMLフォーム)を返す
  B->>SP: POST /auth/saml/callback(SAMLアサーション送信)
  note over SP: SamlAuthGuard起動<br>passport-samlが署名・期限を検証<br>SamlStrategy.validate()でreq.userをセット
  note over SP: AuthService.createToken()でJWT生成
  note over SP: res.cookie()でHttpOnly CookieにJWTをセット
  SP->>B: 302リダイレクト(SPAへ)CookieつきSPA->>SP: GET /auth/me(CookieにJWTを含む)
  note over SP: JwtCookieAuthGuard起動<br>JwtCookieStrategy がCookieからJWT取得・検証<br>validate()でreq.userをセット
  SP->>B: ユーザー情報を返す

NestJSでの責務分担

実装に入る前に、今回登場する主要クラスの責務を整理しておく。

クラス役割
ControllerHTTPリクエストの入口
Guard認証処理の起動・認可判定
Strategy認証ロジックの実装
ServiceJWT生成やユーザー関連処理
PassportGuardとStrategyを橋渡しする仕組み

認証処理を初めて読むと、Controllerに認証ロジックが見当たらず戸惑うことがある。

例えば以下のコード。

@Get("saml/login")
@UseGuards(SamlAuthGuard)
samlLogin() {}

メソッド本体は空だが、実際には SamlAuthGuard が認証処理を開始する。

つまり、

Controller
 ↓
Guard
 ↓
Strategy
 ↓
req.user
 ↓
Service

という流れで処理が進む。

以降の実装では、この責務分担を意識しながら読むと理解しやすい。

この記事でやること

  • テスト用SAML IdPをDockerで起動する
  • passport-saml を使って SamlStrategy を実装する
  • SamlAuthGuard でSAML認証フローを制御する
  • AuthService でJWTを生成する
  • JWTをHttpOnly Cookieに保存する
  • JwtCookieStrategy / JwtCookieAuthGuard でCookieからJWTを検証する
  • /auth/me でSPAからユーザー情報を取得できるようにする

ディレクトリ構成

api-sample/
├─ .env
└─ src/
   ├─ main.ts                         # cookie-parser / CORS を追加
   ├─ app.module.ts                   # AuthModule を追加
   └─ auth/
      ├─ auth.controller.ts           # /auth/saml/login, /auth/saml/callback, /auth/me
      ├─ auth.module.ts
      ├─ auth.service.ts              # JWT生成ロジック
      ├─ saml.strategy.ts             # SAMLアサーション検証 / req.user の形を決める
      ├─ saml-auth.guard.ts           # SAMLリダイレクト / コールバック用 Guard
      ├─ jwt-cookie.strategy.ts       # Cookie から JWT を取り出して検証する Strategy
      └─ jwt-cookie.guard.ts          # JWT Cookie 検証を使う Guard

触るファイル一覧

api-sample/
├─ .env                               # SAML / JWT / SPA の環境変数を追加
└─ src/
   ├─ main.ts                         # 変更: cookieParser / enableCors を追加
   ├─ app.module.ts                   # 変更: AuthModule を追加
   └─ auth/                           # 新規作成
      ├─ auth.controller.ts
      ├─ auth.module.ts
      ├─ auth.service.ts
      ├─ saml.strategy.ts
      ├─ saml-auth.guard.ts
      ├─ jwt-cookie.strategy.ts
      └─ jwt-cookie.guard.ts

1. パッケージを追加する

pnpmの場合。

pnpm add @nestjs/passport passport passport-saml @nestjs/jwt passport-jwt cookie-parser
pnpm add -D @types/passport-saml @types/passport-jwt @types/cookie-parser

npmの場合。

npm install @nestjs/passport passport passport-saml @nestjs/jwt passport-jwt cookie-parser
npm install -D @types/passport-saml @types/passport-jwt @types/cookie-parser

2. 環境変数を追加する

編集するファイルは api-sample/.env

# JWT
JWT_SECRET="dev-secret-change-me"
JWT_EXPIRES_IN="1h"

# SAML(接続先IdPに合わせて変更する)
SAML_ENTRY_POINT="http://localhost:8080/simplesaml/saml2/idp/SSOService.php"
SAML_ISSUER="urn:my-nestjs-app"
SAML_CALLBACK_URL="http://localhost:3000/auth/saml/callback"
SAML_CERT="MIIBkTCB+wIJAJC..."

# SPA
SPA_URL="http://localhost:5173"

SAML_ENTRY_POINT はIdPのSSOエンドポイントURL。SAML_ISSUER はSPのエンティティIDで、IdP側の設定と一致している必要がある。SAML_CERT はIdPの公開鍵証明書(Base64文字列)で、後述の手順で取得する。

3. テスト用IdPをDockerで起動する

実際のSAML認証を動かすにはIdPが必要。学習用にはDocker版のSimpleSAMLphpが手軽に使える。

docker run -d \
  --name saml-idp \
  -p 8080:8080 \
  -e SIMPLESAMLPHP_SP_ENTITY_ID=urn:my-nestjs-app \
  -e SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=http://localhost:3000/auth/saml/callback \
  kristophjunge/test-saml-idp

起動後、http://localhost:8080/simplesaml にアクセスして動作を確認できる。

SAML_CERT はIdPのメタデータから取得する。

curl -s http://localhost:8080/simplesaml/saml2/idp/metadata.php \
  | grep -oP '(?<=<ds:X509Certificate>)[^<]+'

出力された文字列をそのまま .envSAML_CERT に入れる。

テストユーザーはIdPのデフォルト設定に含まれている。

ユーザー名パスワード
user1user1pass
user2user2pass

4. SamlStrategy を作る

新規作成するファイルは api-sample/src/auth/saml.strategy.ts

SamlStrategy は「SAMLのアサーションをどう検証し、req.user に何を入れるか」をPassportに教える設定クラス。

  • super() の中が SAML接続設定(どのIdPへ、どの証明書で検証するか)
  • validate()アサーション検証後の処理(プロファイルをアプリで使うユーザー型に変換する)
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-saml";

// SPAやJWTに渡すユーザー情報の型
export type SamlUser = {
  nameId: string;
  email: string;
  displayName: string;
};

// SAMLアサーションのプロファイル型(IdPにより属性名が異なる)
type SamlProfile = {
  nameID?: string;
  email?: string;
  displayName?: string;
  [key: string]: string | string[] | undefined;
};

@Injectable()
export class SamlStrategy extends PassportStrategy(Strategy, "saml") {
  constructor() {
    super({
      // IdPのSSOエンドポイントURL
      entryPoint: process.env.SAML_ENTRY_POINT,

      // このSPを識別するエンティティID(IdP側の設定と合わせる)
      issuer: process.env.SAML_ISSUER,

      // IdPがSAMLアサーションをPOSTするURL
      callbackUrl: process.env.SAML_CALLBACK_URL,

      // IdPの公開鍵証明書(署名の検証に使う)
      cert: process.env.SAML_CERT ?? "",
    });
  }

  // SAMLアサーションの署名と内容が正しいと検証された後に呼ばれる
  // 戻り値がそのまま req.user にセットされる
  validate(profile: SamlProfile): SamlUser {
    return {
      // nameIDはIdPがユーザーを一意に識別する値
      nameId: profile.nameID ?? "",

      // 属性名はIdPにより異なる。SimpleSAMLphpは "email"、Azure ADは長いURIになる
      email: (profile.email as string) ?? "",

      // 表示名。IdPの設定に合わせて調整する
      displayName: (profile.displayName as string) ?? "",
    };
  }
}

profile の属性名はIdPによって異なる。Azure ADなら http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress、SimpleSAMLphpなら email のように、接続するIdPのドキュメントを確認して属性名を調整する。

5. AuthService を作る

新規作成するファイルは api-sample/src/auth/auth.service.ts

AuthService はSAMLで認証されたユーザー情報を受け取り、JWTを生成して返す役割に徹する。JWT生成の詳細は JwtService に委譲し、AuthService はその入出力の橋渡しをする。

import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { SamlUser } from "./saml.strategy";

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}

  // SAMLで認証されたユーザー情報からJWT文字列を生成して返す
  // 呼ばれるタイミング: samlCallback() で req.user が確定した直後
  async createToken(user: SamlUser): Promise<string> {
    return this.jwtService.signAsync({
      sub: user.nameId,        // JWT の subject はユーザーの一意識別子
      email: user.email,
      displayName: user.displayName,
    });
  }
}

JWTに何を詰めるかという知識はServiceに集約する。ControllerにJWT生成ロジックを書くと、ControllerがHTTPの入口とJWT仕様の両方を知ることになり責務が混ざる。

6. SamlAuthGuard を作る

新規作成するファイルは api-sample/src/auth/saml-auth.guard.ts

import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class SamlAuthGuard extends AuthGuard("saml") {}

AuthGuard("saml")"saml"SamlStrategy の第2引数で指定した戦略名と揃える。

SamlAuthGuard は同じクラスながら、貼り付けるエンドポイントによって動作が異なる。

場面エンドポイントGuard の動作
ログイン開始GET /auth/saml/loginSAMLリクエストを生成してIdPへ302リダイレクト
コールバックPOST /auth/saml/callbackSAMLアサーションを検証して req.user をセット

どちらも同じ SamlAuthGuard を使っているが、Passportがリクエストのコンテキスト(GETかPOSTか・SAMLアサーションが含まれているか)を判断して処理を自動で切り替える。

7. JwtCookieStrategy を作る

新規作成するファイルは api-sample/src/auth/jwt-cookie.strategy.ts

前の記事JwtStrategyAuthorization: Bearer ヘッダーからJWTを取り出した。SPA + Cookie連携では、CookieからJWTを取り出す専用のStrategyが必要になる。

import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { Request } from "express";

type JwtPayload = {
  sub: string;
  email: string;
  displayName: string;
};

@Injectable()
export class JwtCookieStrategy extends PassportStrategy(Strategy, "jwt-cookie") {
  constructor() {
    super({
      // Cookie の "access_token" キーから JWT を取り出す
      jwtFromRequest: ExtractJwt.fromExtractors([
        (req: Request) => req?.cookies?.["access_token"] ?? null,
      ]),

      // 期限切れの JWT は拒否する
      ignoreExpiration: false,

      // AuthService でJWT生成に使った秘密鍵と同じ値で検証する
      secretOrKey: process.env.JWT_SECRET ?? "dev-secret-change-me",
    });
  }

  // 署名と期限の検証が通った後に呼ばれる
  // 戻り値が req.user にセットされる
  validate(payload: JwtPayload) {
    return {
      userId: payload.sub,
      email: payload.email,
      displayName: payload.displayName,
    };
  }
}

ExtractJwt.fromExtractors に関数の配列を渡すと、先頭から順に試してJWTが取れた時点で検証に進む。Cookieが見つからない場合は null を返すと認証失敗として扱われる。

8. JwtCookieAuthGuard を作る

新規作成するファイルは api-sample/src/auth/jwt-cookie.guard.ts

import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class JwtCookieAuthGuard extends AuthGuard("jwt-cookie") {}

"jwt-cookie"JwtCookieStrategy の第2引数で指定した戦略名と揃える。

9. AuthController を作る

新規作成するファイルは api-sample/src/auth/auth.controller.ts

AuthController は認証フローのHTTP入口。3つのエンドポイントを提供する。

エンドポイント役割
GET /auth/saml/loginSAMLログイン開始。SamlAuthGuard がIdPへリダイレクト
POST /auth/saml/callbackIdPからのSAMLアサーションを受信。JWT生成・Cookie保存・SPAへリダイレクト
GET /auth/meJWTが有効なときのみユーザー情報を返す
import {
  Controller,
  Get,
  Post,
  Req,
  Res,
  UseGuards,
} from "@nestjs/common";
import { Request, Response } from "express";
import { AuthService } from "./auth.service";
import { SamlAuthGuard } from "./saml-auth.guard";
import { JwtCookieAuthGuard } from "./jwt-cookie.guard";
import { SamlUser } from "./saml.strategy";

type AuthenticatedRequest = Request & {
  user: { userId: string; email: string; displayName: string };
};

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

  // SAMLログイン開始
  // SamlAuthGuardがSAMLリクエストを生成してIdPへリダイレクトするため
  // このメソッド本体には到達しない
  @UseGuards(SamlAuthGuard)
  @Get("saml/login")
  samlLogin(): void {
    // Guard がIdPへリダイレクトするのでここには来ない
  }

  // SAMLコールバック
  // IdPが認証後にSAMLアサーションをPOSTしてくる
  @UseGuards(SamlAuthGuard)
  @Post("saml/callback")
  async samlCallback(
    @Req() req: Request & { user: SamlUser },
    @Res() res: Response,
  ): Promise<void> {
    // この時点で SamlAuthGuard がアサーションを検証済みで req.user に SamlUser が入っている

    // JWT生成タイミング: ここで AuthService に委譲する
    const token = await this.authService.createToken(req.user);

    // Cookie保存タイミング: JWTをHttpOnly CookieにセットしてSPAに渡す
    res.cookie("access_token", token, {
      httpOnly: true,                                      // JavaScriptからアクセス不可
      secure: process.env.NODE_ENV === "production",       // 本番ではHTTPS必須
      sameSite: "lax",                                     // CSRF対策
      maxAge: 60 * 60 * 1000,                             // 1時間(ミリ秒)
    });

    // Cookieをセットしたうえで SPA へリダイレクト
    res.redirect(process.env.SPA_URL ?? "http://localhost:5173");
  }

  // SPA向けのユーザー情報取得API
  // JwtCookieAuthGuard が Cookie から JWT を取り出して検証する
  @UseGuards(JwtCookieAuthGuard)
  @Get("me")
  me(@Req() req: AuthenticatedRequest) {
    // JwtCookieStrategy.validate() の戻り値が req.user に入っている
    return req.user;
  }
}

samlCallback@Res() を使っているのは、res.cookie()res.redirect() がExpressの低レベルAPIに依存するため。@Res() を付けると NestJS の自動レスポンス処理が無効になるので、メソッド内で明示的にレスポンスを完了させる必要がある。

10. AuthModule を作る

新規作成するファイルは api-sample/src/auth/auth.module.ts

import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { SamlStrategy } from "./saml.strategy";
import { JwtCookieStrategy } from "./jwt-cookie.strategy";

@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET ?? "dev-secret-change-me",
      signOptions: { expiresIn: process.env.JWT_EXPIRES_IN ?? "1h" },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, SamlStrategy, JwtCookieStrategy],
})
export class AuthModule {}

SamlStrategyJwtCookieStrategy はどちらも providers に登録する。DIシステムがインスタンス化してPassportに戦略として登録する流れはフレームワーク側が自動でやってくれる。

編集するファイルは api-sample/src/main.ts

cookie-parser を追加しないと req.cookiesundefined になりJWT取得に失敗する。CORS設定の credentials: true がないとブラウザがCookieを送受信しない。

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import * as cookieParser from "cookie-parser";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // req.cookies を使えるようにする
  app.use(cookieParser());

  // SPA からのクロスオリジンリクエストで Cookie を送受信できるようにする
  app.enableCors({
    origin: process.env.SPA_URL ?? "http://localhost:5173",
    credentials: true,
  });

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

12. AppModuleAuthModule を追加する

編集するファイルは api-sample/src/app.module.ts

import { Module } from "@nestjs/common";
import { AuthModule } from "./auth/auth.module"; // 追加
// ... 既存のimport

@Module({
  imports: [
    AuthModule, // 追加
    // ... 既存のModule
  ],
})
export class AppModule {}

13. SPAとの連携

SPAからAPIを呼ぶ場合、fetchaxioscredentials: "include" を指定してCookieをリクエストに含める。

// SPA 側(TypeScript / React など)

// ユーザー情報の取得
const res = await fetch("http://localhost:3000/auth/me", {
  credentials: "include", // Cookie を Cross-Origin リクエストに含める
});
const user = await res.json();
// → { userId: "...", email: "user1@example.com", displayName: "User One" }
// 認証が必要な任意のAPIを叩く場合も同様
const res = await fetch("http://localhost:3000/some-protected-endpoint", {
  credentials: "include",
});

credentials: "include"fetch のデフォルトが "same-origin"(同一オリジンのみCookieを送る)のため明示的に指定が必要。axios の場合は withCredentials: true が対応する設定。

14. 動作確認

アプリを起動する。

pnpm start:dev

ブラウザで http://localhost:3000/auth/saml/login にアクセスすると、SimpleSAMLphpのログイン画面へリダイレクトされる。

user1 / user1pass でログインする。

ログイン成功後、http://localhost:5173SPA_URL)へリダイレクトされる。ブラウザの開発者ツール → Application → Cookies で access_token が保存されていることを確認する。

次のコマンドでブラウザから取得したCookieを使って /auth/me を確認できる。

curl -s http://localhost:3000/auth/me \
  --cookie "access_token=<ブラウザのDevToolsからコピーしたJWT>"

レスポンス例。

{
  "userId": "user1@example.com",
  "email": "user1@example.com",
  "displayName": "User One"
}

これは JwtCookieStrategy.validate() が返した値が req.user に入り、GET /auth/me でそのまま返っているため。

Cookieなしで叩くと401になる。

curl -i http://localhost:3000/auth/me
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
{
  "statusCode": 401,
  "message": "Unauthorized"
}

コードリーディングのポイント

実務でSAML認証を調査するときは、Controllerから読むよりもアプリケーションの起動点から追う方が全体像を把握しやすい。

main.ts
 ↓
AppModule
 ↓
AuthModule
 ↓
AuthController
 ↓
SamlAuthGuard
 ↓
SamlStrategy
 ↓
req.user
 ↓
AuthService

特に認証系は「Controllerに処理が書かれていない」ことが珍しくない。

そのため、以下の順番で追うと迷いにくい。

  1. Controller(入口)
  2. Guard(どの認証戦略を使うか)
  3. Strategy(認証ロジック本体)
  4. req.user(認証結果)
  5. Service(JWT生成やユーザー処理)
  6. Cookie設定箇所
  7. 認証済みAPI

実案件でも同じ見方ができるため、学習用サンプルを読む際にも意識してみてほしい。

責務の整理

各クラスの担当をまとめる。

SamlStrategy の役割

  • super()SAML接続設定を渡す(どのIdPへ・どの証明書で検証するか)
  • validate() でアサーション検証後のプロファイルを受け取り、アプリで使う SamlUser 型に変換する
  • validate() の戻り値が req.user にセットされる

SAMLのXML解析・署名検証・リダイレクトURLの生成などの複雑な処理は passport-saml が担う。StrategyはIdP設定とユーザーマッピングだけを書けばよい。

SamlAuthGuard の役割

  • GET /auth/saml/login に付けることで、PassportにSAMLリクエストの生成とIdPへのリダイレクトをさせる
  • POST /auth/saml/callback に付けることで、PassportにSAMLアサーションの検証と req.user のセットをさせる
  • Guardは「このエンドポイントでこの戦略を使う」という宣言。検証ロジック本体はStrategyに委譲している

AuthService の役割

  • SAMLで認証されたユーザー情報(SamlUser)を受け取り、JWTを生成して返す
  • JWTのペイロードに何を含めるかという知識をここに集約する
  • Controllerからは「JWT文字列を返すService」としてのみ扱う

AuthController の役割

  • HTTPリクエストの入口(URL・HTTPメソッド)を定義する
  • Guardを宣言してどの認証戦略を適用するかを指定する
  • samlCallback ではJWT生成・Cookie保存・リダイレクトの流れを指示するだけで、各処理の詳細はServiceや res に任せる

req.user がセットされる流れ

【SAMLログイン時】
POST /auth/saml/callback(SAMLアサーション付き)
  → SamlAuthGuard が起動
  → passport-saml が XML署名・期限を検証
  → SamlStrategy.validate(profile) が呼ばれる
  → validate() の戻り値(SamlUser)が req.user にセットされる
  → AuthController.samlCallback(req) に到達(req.user が使える状態)

【APIアクセス時】
GET /auth/me(Cookie に JWT を含む)
  → JwtCookieAuthGuard が起動
  → JwtCookieStrategy が req.cookies["access_token"] から JWT を取り出す
  → passport-jwt が署名・期限を検証
  → JwtCookieStrategy.validate(payload) が呼ばれる
  → validate() の戻り値({ userId, email, displayName })が req.user にセットされる
  → AuthController.me(req) に到達(req.user が使える状態)

JWT生成タイミングとCookie保存タイミング

POST /auth/saml/callback
  → SamlAuthGuard が req.user をセット(SAMLアサーションの検証完了)
  → AuthController.samlCallback() に入る
  → AuthService.createToken(req.user) でJWTを生成 ← JWT生成タイミング
  → res.cookie("access_token", token, { httpOnly: true, ... }) ← Cookie保存タイミング
  → res.redirect(SPA_URL) でSPAへ

よくあるエラー

Error: SAML certificate is required

.envSAML_CERT が空になっている。DockerのIdPから証明書を取得して設定する。

curl -s http://localhost:8080/simplesaml/saml2/idp/metadata.php \
  | grep -oP '(?<=<ds:X509Certificate>)[^<]+'

InvalidSignatureError

SAML_CERT の値が正しくない。BEGIN CERTIFICATE / END CERTIFICATE ヘッダー付きのPEM形式か、ヘッダーなしのBase64文字列かはIdPにより異なる。passport-saml は通常ヘッダーなしのBase64を期待する。

req.cookies is undefined

main.tsapp.use(cookieParser()) が追加されていない。または import * as cookieParser from "cookie-parser" が抜けている。

CORS設定で credentials: true が漏れている可能性がある。SPA側も credentials: "include" が必要。どちらか片方だけでは機能しない。

/auth/saml/callback で404になる

AppModuleAuthModule が追加されているか確認する。また @Post("saml/callback")@Controller("auth") の中にあることを確認する。

IdP にリダイレクトされない / エンティティIDが一致しない

SAML_ENTRY_POINT / SAML_ISSUER / SAML_CALLBACK_URL がIdP側の設定と一致しているか確認する。Dockerで起動したIdPの場合、SIMPLESAMLPHP_SP_ENTITY_IDSAML_ISSUER が一致している必要がある。

実装タスク(チェックリスト)

  • passport-saml / cookie-parser などのパッケージをインストール
  • .envSAML_* / JWT_SECRET / SPA_URL を追加
  • DockerでテストIdPを起動し、SAML_CERT を取得して .env に入れる
  • src/auth/saml.strategy.ts を作成(SamlStrategy / SamlUser
  • src/auth/auth.service.ts を作成(AuthService.createToken
  • src/auth/saml-auth.guard.ts を作成(SamlAuthGuard
  • src/auth/jwt-cookie.strategy.ts を作成(JwtCookieStrategy
  • src/auth/jwt-cookie.guard.ts を作成(JwtCookieAuthGuard
  • src/auth/auth.controller.ts を作成(3エンドポイント)
  • src/auth/auth.module.ts を作成
  • src/main.tscookieParser()enableCors() を追加
  • src/app.module.tsAuthModule を追加
  • ブラウザで GET /auth/saml/login → SimpleSAMLphpログイン → Cookie保存を確認
  • GET /auth/me でユーザー情報が返ることを確認
  • GET /auth/me(Cookieなし)で401になることを確認

参考