コンテンツにスキップ

認証仕様(共通)

本ドキュメントと合わせて以下を参照すること。

ドキュメント内容
ArchitecturePolicy_Common.md共通アーキテクチャ方針
CodingConventions_Common.md共通コーディング規約
機能別認証仕様(例:AuthenticationSpecification_Coordination_Sprint1.md)機能・フェーズ固有の認証実装方針・コード例

本ドキュメントは、機能Aおよび関連APIにおける認証・認可仕様を定義する。

本システムでは、認証ロジックをアプリケーション本体から分離し、別プロセスである auth-service が以下を一括して担う。

  • 複数OIDC IdPへの対応
  • 外部JWT / ID Token の検証
  • ユーザー解決
  • 組織解決
  • 権限解決
  • 内部JWTの発行
  • APIリクエストのプロキシ
  • APIルート単位の認可チェック

アプリケーション本体、以降 バックエンドサービス(Java Spring Boot)と呼ぶ、は原則として外部IdPに直接依存しない。

フェーズ別認証実装について: 認証ロジックの実装スケジュールの都合により、機能Aの実装は段階的に行う。auth-service の実装が完了した後、バックエンドサービス側の認証連携を追加実装する(詳細は第 24 節参照)。


認証・認可の全体構成は以下とする。

Browser
Caddy
auth-service
バックエンドサービス(Java Spring Boot)
PostgreSQL

各コンポーネントの責務は以下の通りとする。

コンポーネント責務
Browser利用者操作、アクセストークンまたはID Tokenの送信
CaddyHTTPS終端、リバースプロキシ、auth-serviceへの転送
auth-serviceOIDC検証、ユーザー解決、RBAC解決、内部JWT発行、APIプロキシ
バックエンドサービス業務API処理、内部JWTの検証、業務ロジック、DBアクセス
PostgreSQLユーザー、組織、権限、業務データの保持、RLS適用

Note: 本アーキテクチャは ADR-002「提案(未決定・内部議論中)」段階であり、確定前に変更される可能性がある。


本システムでは、外部認証方式としてOIDCを使用する。

現時点ではOIDCのみを対象とするが、将来的にSAML IdPへ対応する可能性を考慮し、IdPごとの設定はDBで管理する。

本システムでは、AWS ALB OIDC認証は使用しない。

理由は以下の通り。

  • ALB OIDCはリスナー単位で単一IdPを前提とする
  • 本システムでは組織ごとに異なるIdPを利用する
  • 複数IdPを動的に切り替える必要がある
  • 認証後にDB上のユーザー、組織、権限へマッピングする必要がある

そのため、認証・認可処理は auth-service で実装する。


本仕様の対象範囲は以下とする。

  • OIDC IdP設定管理
  • OIDC Token検証
  • issuerによるIdP動的選択
  • subjectによる内部ユーザー解決
  • 組織解決
  • ロール解決
  • Permission解決
  • 内部JWT発行
  • APIルート単位の認可チェック
  • バックエンドサービスへのプロキシ
  • PostgreSQL RLS連携
  • JWT署名鍵管理

以下は現時点では対象外とする。

  • SAML認証の実装
  • ユーザー登録画面
  • IdP管理画面
  • パスワード認証
  • MFAの独自実装
  • OAuth2 Authorization Serverの独自実装
  • ALB OIDCによる認証
  • フロントエンド画面実装

ただし、将来的なSAML対応を妨げない設計とする。


外部APIアクセス時(他社USS向け: <basePath>/uss/v1/feature-a/、UI向け: <basePath>/api/v1/feature-a/)の基本フローは以下とする。basePath = /utmserver.servlet.context-path で可変)。

1. Browser が Caddy に API リクエストを送信する
2. Caddy が auth-service にリクエストを転送する
3. auth-service が外部JWTまたはID Tokenを取得する
4. auth-service が token の iss クレームを確認する
5. auth-service が iss に対応する IdP 設定を idp_connections から取得する
6. auth-service が IdP の JWKS を取得し、外部JWTを検証する
7. auth-service が token の subject を取得する
8. auth-service が subject をDBで検索し、内部 user_id / org_id を解決する
9. auth-service が roles / permissions をDBから解決する
10. auth-service が要求APIに必要なPermissionを確認する
11. 認可OKの場合、auth-service が内部JWTを発行する
12. auth-service が内部JWTを付与してバックエンドサービスにリバースプロキシする
13. バックエンドサービスが内部JWTを検証する
14. バックエンドサービスがDBトランザクション開始時に SET ROLE / SET app.current_user_id を実行する
15. PostgreSQL RLS により行レベルのアクセス制御を行う

内部API(/api/v1/internal/feature-a/)は認証不要であり、auth-service を経由しない(または auth-service でスルー扱いとする)。


auth-service は認証・認可の中心コンポーネントとして、以下を担当する。

  • 外部IdPの動的選択
  • OIDC Token検証
  • JWKS取得
  • ユーザー解決
  • 組織解決
  • ロール解決
  • Permission解決
  • APIルート単位のPermissionチェック
  • 内部JWT発行
  • 内部JWT署名鍵管理
  • バックエンドサービスへのリバースプロキシ
  • 認証・認可エラー応答

auth-service は、業務ロジックを実装しない。


7. バックエンドサービスの責務

Section titled “7. バックエンドサービスの責務”

バックエンドサービス(Java Spring Boot)は業務アプリケーション本体として、以下を担当する。

  • 内部JWTの検証(Spring Security による JWT フィルター実装)
  • 内部JWTから user_id / org_id / permissions を取得し SecurityContext に保持
  • 業務API処理
  • 業務ロジック
  • DBトランザクション制御(TransactionManager.execute を使用)
  • PostgreSQL RLS用のセッション変数設定(TransactionSessionInitializer 経由)
  • DBアクセス(MyBatis XML マッパー)

バックエンドサービス は、外部IdPとのOIDC検証を直接行わない。

バックエンドサービス は、外部JWTまたは外部ID Tokenを信頼しない。

バックエンドサービス が信頼するのは、auth-service が発行した内部JWTのみとする。

ただし、内部API(/api/v1/internal/feature-a/)は認証を必要としない(要確認事項 1.5 参照)。

フェーズ別認証実装: 認証連携実装前は、JWT検証フィルターおよびRLS設定は no-op とする(第 24 節参照)。


本システムでは、組織ごとに異なるOIDC IdPを利用できるものとする。

IdP設定はDBの idp_connections テーブルで管理する。

auth-service は、受信した外部JWTの iss クレームを確認し、対応するIdP設定を動的に選択する。

この機能を MultiProvider Router と呼ぶ。


MultiProvider Router は、外部JWTの iss クレームをもとに、利用すべきOIDC Provider設定を動的に選択する。

  • 外部JWT
  • 外部ID Token
  • Authorizationヘッダ
  • 必要に応じてCookie
1. token をパースする
2. token の iss クレームを取得する
3. idp_connections から issuer に一致するIdP設定を取得する
4. IdP設定が存在しない場合は 401 Unauthorized を返却する
5. IdP設定が無効な場合は 401 Unauthorized を返却する
6. 対応する OIDC Verifier に検証を委譲する

iss は外部IdPを識別するための主キー相当の値として扱う。

例:

{
"iss": "https://idp.example.com/oauth2/default",
"sub": "00u123456789",
"aud": "client-id",
"exp": 1760000000,
"iat": 1759999100
}

IdP接続情報は idp_connections テーブルで管理する。

想定カラムは以下とする。

カラム名内容
idIdP接続ID
org_id組織ID
provider_typeIdP種別
issuerOIDC issuer
client_idOIDC client_id
jwks_uriJWKS URI
authorization_endpoint認可エンドポイント
token_endpointトークンエンドポイント
userinfo_endpointUserInfoエンドポイント
enabled有効フラグ
created_at作成日時
updated_at更新日時

provider_type は以下を想定する。

内容
oidcOIDC IdP
samlSAML IdP、将来対応用

現時点では provider_type = 'oidc' のみを実装対象とする。


OIDC Verifier は、外部IdPが発行したJWTまたはID Tokenを検証する。

本コンポーネントは auth-service が実装する。バックエンドサービスの実装対象外である。

11.2 使用ライブラリ(auth-service固有)

Section titled “11.2 使用ライブラリ(auth-service固有)”

auth-service(Go)のOIDC検証には go-oidc v3 を使用する。

本項は auth-service(Go)固有の仕様であり、バックエンドサービス(Java Spring Boot)の実装対象外である。

OIDC Verifierでは、少なくとも以下を検証する。

  • 署名
  • issuer
  • audience
  • expiration time
  • issued at
  • not before
  • token形式
  • JWKSとの整合性
  • IdP設定が有効であること

JWKSはIdP設定に基づいて取得する。

JWKSの取得元は以下のいずれかとする。

  • OIDC discovery documentから取得
  • idp_connections.jwks_uri から取得

JWKSは毎リクエスト取得せず、キャッシュする。

キャッシュ期間および更新方針はIdPのレスポンスヘッダ、またはauth-serviceの設定に従う。


User Resolver は、外部IdPから返却された subject を内部ユーザーへマッピングする。

本コンポーネントは auth-service が実装する。バックエンドサービスの実装対象外である。

  • issuer
  • subject
  • organization hint
  • email
  • external claims
1. issuer に対応する idp_connection を取得する
2. token の sub を取得する
3. idp_connection_id と sub をキーにユーザーを検索する
4. ユーザーが存在しない場合は 403 Forbidden を返却する
5. ユーザーが無効な場合は 403 Forbidden を返却する
6. ユーザーに紐づく org_id を取得する
7. 組織が無効な場合は 403 Forbidden を返却する
8. user_id / org_id を認証済みプリンシパルとして確定する

ユーザー解決では、以下のテーブルを使用する。

  • idp_connections
  • users
  • organizations

必要に応じて、外部IdPのsubjectを管理する専用テーブルを設けてもよい。

例:

user_identities

想定カラム:

カラム名内容
idID
user_id内部ユーザーID
idp_connection_idIdP接続ID
external_subjectIdPのsubject
external_emailIdP上のemail
enabled有効フラグ
created_at作成日時
updated_at更新日時

本システムでは、認可方式としてRBACを採用する。

RBACは以下の概念で構成する。

概念内容
User利用者
Organization組織
Roleロール
Permission権限
RolePermissionロールと権限の対応
UserRoleユーザーとロールの対応

RBAC解決では、以下のテーブルを使用する。

  • users
  • organizations
  • roles
  • permissions
  • user_roles
  • role_permissions

想定構造は以下とする。

users
└─ user_roles
└─ roles
└─ role_permissions
└─ permissions

auth-service は、認証済みユーザーの user_id および org_id をもとに、有効なPermission一覧を取得する。


16. Permission命名規約(判断不可。要確認。)

Section titled “16. Permission命名規約(判断不可。要確認。)”

Permission名は、以下の形式を基本とする。

<resource>:<action>

機能Aにおける具体的なリソース名(<resource>)および必要なアクション(<action>)は、OpenAPI yaml のエンドポイント設計確定後に決定する。(判断不可。要確認。)

参考:汎用的な命名パターン例

<resource>:read
<resource>:create
<resource>:update
<resource>:delete
<resource>:approve

APIルートごとに必要なPermissionを定義する。


auth-service/uss/v1/feature-a/* へのリクエストを バックエンドサービス にプロキシする前に、ルートごとのPermissionチェックを行う。

1. リクエストメソッドとパスを取得する
2. 認可設定から必要Permissionを取得する
3. 認証済みユーザーが該当Permissionを持つか確認する
4. Permissionが不足している場合は 403 Forbidden を返却する
5. Permissionが十分な場合はバックエンドサービスへプロキシする

内部API(/api/v1/internal/feature-a/)はPermissionチェックを行わない。


18. APIルート別Permission定義例(判断不可。要確認。)

Section titled “18. APIルート別Permission定義例(判断不可。要確認。)”

以下はPermission定義の記述パターン例である。

実際の機能AのPermission定義は、OpenAPI yaml(他社USS向け外部API /uss/v1/feature-a/、UI向け外部API /api/v1/feature-a/、および内部API /api/v1/internal/feature-a/ のエンドポイント定義)の確定後に決定する。(判断不可。要確認。)

参考(汎用パターン例):

他社USS向け外部API(<basePath>/uss/v1/feature-a/

HTTP MethodPath パターン必要Permission パターン
GET/uss/v1/feature-a/…<resource>:read
POST/uss/v1/feature-a/…<resource>:create
PUT/uss/v1/feature-a/…/{id}<resource>:update
DELETE/uss/v1/feature-a/…/{id}<resource>:delete
POST/uss/v1/feature-a/…/{id}/approve<resource>:approve

UI向け外部API(<basePath>/api/v1/feature-a/

HTTP MethodPath パターン必要Permission パターン
GET/api/v1/feature-a/…<resource>:read
POST/api/v1/feature-a/…<resource>:create
PUT/api/v1/feature-a/…/{id}<resource>:update
DELETE/api/v1/feature-a/…/{id}<resource>:delete

内部API(<basePath>/api/v1/internal/feature-a/)は認証・認可不要とする(要確認事項 1.5 参照)。


auth-service は、外部JWTの検証およびユーザー・権限解決に成功した後、内部JWTを発行する。

内部JWTは バックエンドサービス が信頼する唯一の認証情報とする。

内部JWTの署名方式は以下とする。

RS256

秘密鍵は auth-service のみが保持する。

公開鍵は、内部JWTを検証する必要があるコンポーネントに配布する。


内部JWTのTTLは以下とする。

15分

TTLを短くする理由は以下の通り。

  • 権限変更の反映遅延を抑える
  • 漏洩時の影響範囲を限定する
  • 長期セッション管理を外部IdPまたはフロントエンド側に委ねる

内部JWTには以下のClaimを含める。

Claim内容
iss内部JWT発行者
sub内部ユーザーID
org_id組織ID
permissionsPermission一覧
rolesロール一覧
idp_connection_idIdP接続ID
external_sub外部IdPのsubject
iat発行日時
exp有効期限
jtiJWT ID

例:

{
"iss": "auth-service",
"sub": "user-123",
"org_id": "org-001",
"roles": [
"featureA-admin"
],
"permissions": [
"featureA:read",
"featureA:create",
"featureA:update"
],
"idp_connection_id": "idp-001",
"external_sub": "00u123456789",
"iat": 1760000000,
"exp": 1760000900,
"jti": "jwt-unique-id"
}

auth-serviceバックエンドサービス へリクエストをプロキシする際、内部JWTをHTTPヘッダに付与する。

使用するヘッダは以下とする。

Authorization: Bearer <internal-jwt>

また、必要に応じて以下の内部ヘッダを付与してもよい。

X-User-Id: <user_id>
X-Org-Id: <org_id>
X-Permissions: <comma-separated-permissions>

ただし、バックエンドサービス は内部ヘッダのみを信頼してはならない。

バックエンドサービス は必ず内部JWTを検証し、JWTのClaimを正とする。


23. バックエンドサービス側のInternal JWT検証

Section titled “23. バックエンドサービス側のInternal JWT検証”

バックエンドサービス は、受信した内部JWTについて以下を検証する。

  • 署名
  • issuer
  • expiration time
  • issued at
  • audience、必要な場合
  • token形式
  • 必須Claimの存在
  • subの存在
  • org_idの存在
  • permissionsの存在

内部JWTが存在しない、または不正な場合は RFC 9457 Problem Details 形式で 401 Unauthorized を返却する。

Permissionが不足している場合は、原則として RFC 9457 Problem Details 形式で 403 Forbidden を返却する。

23.1 Java Spring Boot での実装方針(判断不可。要確認。)

Section titled “23.1 Java Spring Boot での実装方針(判断不可。要確認。)”

バックエンドサービス(Java Spring Boot)における内部JWT検証の具体的な実装方式は以下のいずれかとする。(判断不可。要確認。)

  • 方式A: Spring Security OAuth2 Resource Server + Nimbus JOSE + JWT(spring-security-oauth2-resource-server 依存)
  • 方式B: OncePerRequestFilter を継承したカスタム JWT フィルター + Nimbus JOSE + JWT

いずれの方式でも、検証成功後は SecurityContextHolder にユーザー情報(user_idorg_idpermissions)を格納する。

23.2 公開鍵の取得・設定方法(判断不可。要確認。)

Section titled “23.2 公開鍵の取得・設定方法(判断不可。要確認。)”

バックエンドサービスが内部JWT検証に使用する公開鍵の取得方式は以下のいずれかとする。(判断不可。要確認。)

  • S3 から起動時にダウンロード
  • AWS Systems Manager Parameter Store から取得
  • auth-service の JWKSエンドポイントから動的取得

起動時設定例(ECS Task Definition での環境変数注入):

# application.yaml(将来実装時)
app:
auth:
jwt:
public-key: ${INTERNAL_JWT_PUBLIC_KEY}
issuer: auth-service

機能Aは認証を段階的に実装する。

auth-service の実装スケジュールが確定した後、バックエンドサービス側の認証連携を追加実装する。

認証連携が実装されるまでの間は以下の方針とする。

  • JWT検証フィルターは実装しない(no-op または permitAll で全リクエストを許可する)
  • TransactionSessionInitializer は no-op 実装とし、認証仕様確定後に差し替える
  • PostgreSQL RLSは実装しない
  • SecurityContextHolder にユーザー情報を保持しない

auth-service 実装後、以下を追加実装する。

  1. JWT検証フィルターを実装する(実装方式は第 23.1 節参照)
  2. SecurityContext に user_id / org_id / permissions を格納する
  3. TransactionSessionInitializer を実装し、SET LOCAL ROLESET LOCAL app.current_user_id を実行する
  4. 内部APIの permitAll 設定は維持する(認証不要)
  5. 外部APIには JWT 検証フィルターを適用する

各フェーズにおける具体的な実装方針・コード例は、機能別の認証仕様ドキュメント(例:AuthenticationSpecification_Coordination_Sprint1.md)を参照すること。


認可は以下の2段階で行う。

段階実施コンポーネント内容
APIルート認可auth-serviceHTTP method + path に対するPermissionチェック
業務データ認可バックエンドサービス / PostgreSQLorg_id、user_id、RLS、業務状態に基づく制御

auth-service はAPIに入る前のルート認可を担当する。

バックエンドサービス は業務データ単位の認可を担当する。

PostgreSQL RLSは最終的な行レベル制御を担当する。


本システムでは、PostgreSQLのRow Level Security、以降RLSを使用する。

RLSでは、DBセッションに設定された以下の情報をもとに、参照・更新可能な行を制御する。

  • DB role
  • app.current_user_id
  • app.current_org_id
  • 必要に応じた追加コンテキスト

Sprint1: RLS は実装しない。TransactionSessionInitializer は no-op とする。認証仕様確定後に実装する。


バックエンドサービス は、DBトランザクション開始後、業務SQL実行前に以下を実行する。

SET LOCAL ROLE app_user;
SELECT set_config('app.current_user_id', $1, true);
SELECT set_config('app.current_org_id', $2, true);

SET LOCAL を使用することで、トランザクションスコープにセッション変数を限定し、コネクションプールによるセッション汚染を防ぐ。

PostgreSQLのSET/SET LOCALは値部分にプリペアドステートメントのバインド変数を使えないため、user_idorg_idのような外部由来の値を設定する際は、バインドパラメータを受け付けるset_config(name, value, is_local)関数を使用する(is_local=trueSET LOCAL相当のスコープ)。文字列連結でSQLを組み立てることは禁止する(ArchitecturePolicy_Common.mdCodingConventions_Common.mdのSQLインジェクション禁止方針を参照)。SET LOCAL ROLE app_userのようにアプリケーション側で完全に固定された値(外部入力を含まない)は、文字列連結ではなく固定文字列としてそのまま実行してよい。

RLS用の値は、内部JWTのClaimから取得する。

バックエンドサービスでは、TransactionSessionInitializer がトランザクション開始直後に上記を実行する設計とする。インターフェースは引数を取らない(initializeSession())。実装はJdbcTemplate(またはDataSource)をコンストラクタ注入し、現在のトランザクションに紐づくコネクションを介して実行する(SpringのJdbcTemplateはトランザクション同期済みのコネクションを自動的に使用するため、Connectionをインターフェースの引数として明示的に受け渡す必要はない)。

// TransactionSessionInitializer の設計(認証仕様確定後に実装)
public class TransactionSessionInitializer {
private final JdbcTemplate jdbcTemplate;
private final SecurityContext securityContext;
public void initializeSession() {
var principal = securityContext.getAuthentication().getPrincipal();
jdbcTemplate.execute("SET LOCAL ROLE app_user");
jdbcTemplate.update("SELECT set_config('app.current_user_id', ?, true)", principal.getUserId());
jdbcTemplate.update("SELECT set_config('app.current_org_id', ?, true)", principal.getOrgId());
}
}

RLS用の SET LOCAL ROLE および SET LOCAL app.current_user_id は、トランザクションごとに実行する。

実行タイミングは以下とする。

1. バックエンドサービスが内部JWTを検証する
2. user_id / org_id をリクエストコンテキスト(SecurityContext)に保持する
3. DBトランザクションを開始する(TransactionManager.execute の開始時)
4. TransactionSessionInitializer.initializeSession() を呼び出す
5. SET LOCAL ROLE を実行する
6. set_config('app.current_user_id', ?, true) を実行する(バインドパラメータ使用)
7. set_config('app.current_org_id', ?, true) を実行する(バインドパラメータ使用)
8. 業務SQLを実行する
9. commit または rollback する

コネクションプールを使用する場合、セッション汚染を避けるため、原則として SET LOCAL を使用する。


以下はRLSポリシーの例である。

ALTER TABLE feature_a ENABLE ROW LEVEL SECURITY;
CREATE POLICY feature_a_org_policy
ON feature_a
USING (
org_id = current_setting('app.current_org_id')::uuid
);
CREATE POLICY feature_a_insert_policy
ON feature_a
FOR INSERT
WITH CHECK (
org_id = current_setting('app.current_org_id')::uuid
);

実際のRLSポリシーは、業務テーブルの設計および業務要件に従って定義する。


RLS利用時は以下に注意する。

  • アプリケーション用DBユーザーに過剰な権限を付与しない
  • RLSをバイパス可能なDBロールをアプリで使用しない
  • SET LOCAL ROLE / SET LOCAL の実行漏れを防ぐ
  • コネクションプール利用時のセッション変数残留を防ぐ
  • 管理者用処理では、RLSバイパスの可否を明示的に設計する
  • バッチ処理でRLSを適用するか、別ロールで実行するかを明確にする

内部JWT署名鍵はAWS Secrets Managerで管理する。

秘密鍵は以下で管理する。

AWS Secrets Manager

秘密鍵はKMSで暗号化して保存する。

auth-service のECSタスク起動時に、ECS Task Definitionの secrets ブロックを利用してコンテナへ注入する。

秘密鍵はソースコード、Dockerイメージ、Gitリポジトリに含めない。


ECS Task Definitionでは、Secrets Managerに格納された秘密鍵を環境変数またはファイルとしてコンテナに注入する。

例:

{
"secrets": [
{
"name": "INTERNAL_JWT_PRIVATE_KEY",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:internal-jwt-private-key"
}
]
}

auth-service は起動時に INTERNAL_JWT_PRIVATE_KEY を読み込み、内部JWT署名に使用する。


内部JWTの公開鍵は、検証が必要なコンポーネントへ配布する。

配布先の例:

  • バックエンドサービス
  • 管理API
  • 非同期ワーカー
  • API Gateway相当の内部コンポーネント

公開鍵の配布方式は以下のいずれかとする。

配布方式用途
S3ファイルとして配布する場合
AWS Systems Manager Parameter Store設定値として配布する場合
auth-service の JWKS エンドポイント動的取得する場合

現時点では、S3またはParameter Storeによる配布を想定する。

バックエンドサービスの具体的な公開鍵取得方式は第 23.2 節参照。(判断不可。要確認。)

将来的に複数鍵ローテーションを高度化する場合は、auth-serviceが内部JWKSエンドポイントを提供する方式も検討する。


JWT署名鍵は、Secrets Managerのローテーション機能を利用して運用管理する。

ローテーション時は、既存JWTのTTLが15分であることを考慮し、旧鍵と新鍵の併用期間を設ける。

34.1 ローテーション時の注意事項

Section titled “34.1 ローテーション時の注意事項”
  • 新秘密鍵で署名を開始する
  • バックエンドサービス側に新公開鍵を配布する
  • 旧公開鍵も一定期間保持する
  • 既存内部JWTのTTL満了後に旧鍵を無効化する
  • JWTヘッダには kid を付与することを推奨する

JWTヘッダ例:

{
"alg": "RS256",
"typ": "JWT",
"kid": "2026-06-key-001"
}

内部JWTには、署名鍵識別子として kid を付与することを推奨する。

バックエンドサービスkid を使用して、対応する公開鍵を選択する。

これにより、鍵ローテーション時に複数公開鍵を並行利用できる。


Caddyは外部からのHTTPSリクエストを受け付け、auth-serviceへ転送する。

Caddyでは以下を行う。

  • HTTPS終端
  • リクエスト転送
  • 必要に応じたヘッダ付与
  • パスベースルーティング

Caddyは認証・認可の主処理を行わない。

認証・認可の主処理はauth-serviceで行う。


auth-serviceの Proxy Handler は、認可済みの外部APIリクエスト(<basePath>/uss/v1/feature-a/* および <basePath>/api/v1/feature-a/*)をバックエンドサービスへリバースプロキシする。basePath = /utm

1. <basePath>/uss/v1/feature-a/* または <basePath>/api/v1/feature-a/* リクエストを受け取る
2. 外部JWTを検証する
3. ユーザー・組織・権限を解決する
4. APIルートに必要なPermissionを確認する
5. 内部JWTを発行する
6. Authorizationヘッダに内部JWTを設定する
7. バックエンドサービスへリクエストを転送する
8. バックエンドサービスのレスポンスをBrowserへ返却する

内部API(<basePath>/api/v1/internal/feature-a/*)はPermissionチェックを行わず、スルーする。

auth-serviceは、外部から受け取った認証ヘッダをそのままバックエンドサービスへ転送しない。

バックエンドサービスへ転送する際は、auth-serviceが発行した内部JWTを設定する。

Authorization: Bearer <internal-jwt>

外部JWTをバックエンドサービスへ渡さないことで、バックエンドサービスを外部IdP仕様から分離する。


38.1 auth-service の認証エラー応答(判断不可。要確認。)

Section titled “38.1 auth-service の認証エラー応答(判断不可。要確認。)”

auth-serviceが認証に失敗した場合、401 Unauthorized を返却する。この応答はauth-serviceが返すため、フォーマットはauth-service側の実装に依存する。(判断不可。要確認。)

認証エラーの例:

  • Authorizationヘッダが存在しない
  • Bearer tokenが存在しない
  • token形式が不正
  • issuerが未登録
  • 署名検証に失敗
  • token期限切れ
  • audience不一致
  • IdP設定が無効

38.2 バックエンドサービスの認証エラー応答

Section titled “38.2 バックエンドサービスの認証エラー応答”

バックエンドサービスが内部JWTの検証に失敗した場合、RFC 9457 Problem Details 形式で 401 Unauthorized を返却する。

{
"type": "https://example.com/problems/unauthorized",
"title": "認証に失敗しました",
"status": 401,
"detail": "内部JWTが無効または期限切れです。",
"instance": "/utm/uss/v1/feature-a/xxx"
}

type URI のベースURLは未確定。(要確認事項 1.7 参照。)


39.1 auth-service の認可エラー応答(判断不可。要確認。)

Section titled “39.1 auth-service の認可エラー応答(判断不可。要確認。)”

auth-serviceが認可に失敗した場合、403 Forbidden を返却する。フォーマットはauth-service側の実装に依存する。(判断不可。要確認。)

認可エラーの例(auth-service側):

  • ユーザーが無効
  • 組織が無効
  • ロールが存在しない
  • 必要Permissionを保持していない

39.2 バックエンドサービスの認可エラー応答

Section titled “39.2 バックエンドサービスの認可エラー応答”

バックエンドサービスが業務データ認可で権限不足と判断した場合、RFC 9457 Problem Details 形式で 403 Forbidden を返却する。

{
"type": "https://example.com/problems/forbidden",
"title": "操作が許可されていません",
"status": 403,
"detail": "この操作を実行する権限がありません。",
"instance": "/utm/uss/v1/feature-a/xxx"
}

RLSにより対象データへのアクセスが拒否された場合も、バックエンドサービスは 403 Forbidden を返却する。


認証・認可関連のエラーコードは以下を基本とする。

エラーコードHTTPステータス内容
AUTH_TOKEN_MISSING401トークンが存在しない
AUTH_TOKEN_INVALID401トークン形式または署名が不正
AUTH_TOKEN_EXPIRED401トークン期限切れ
AUTH_ISSUER_UNKNOWN401issuerが未登録
AUTH_IDP_DISABLED401IdP設定が無効
AUTH_USER_NOT_FOUND403内部ユーザーが存在しない
AUTH_USER_DISABLED403ユーザーが無効
AUTH_ORG_DISABLED403組織が無効
AUTH_PERMISSION_DENIED403Permission不足
AUTH_INTERNAL_ERROR500認証基盤内部エラー

auth-serviceでは、認証・認可処理に関するログを出力する。

バックエンドサービスでは、業務処理に関するログとあわせて、JWT検証結果をINFOレベルで記録する。

以下はログ出力してよい。

  • request_id
  • issuer
  • idp_connection_id
  • user_id
  • org_id
  • path
  • method
  • 認証結果
  • 認可結果
  • error_code

以下はログ出力してはならない。

  • 外部JWT全文
  • 内部JWT全文
  • Authorizationヘッダ全文
  • refresh token
  • access token
  • ID Token
  • 秘密鍵
  • APIキー
  • パスワード
  • 個人情報

禁止例:

Authorization: Bearer eyJhbGciOi...

推奨例:

auth failed. request_id=req-001 issuer=https://idp.example.com error_code=AUTH_TOKEN_EXPIRED

必要に応じて、以下の認証・認可イベントを監査ログとして保存する。

  • ログイン成功
  • ログイン失敗
  • issuer未登録
  • ユーザー未登録
  • Permission不足
  • 管理者権限操作
  • 鍵ローテーション
  • IdP設定変更
  • Role変更
  • Permission変更

監査ログには、トークンや秘密情報を保存しない。


認証・認可実装では以下を遵守する。

  • 外部JWTを検証せずに信頼しない
  • バックエンドサービスは外部JWTを直接信頼しない
  • 内部JWTはRS256で署名する
  • 秘密鍵はSecrets Managerで管理する
  • 秘密鍵をソースコードに含めない
  • Permissionチェックを省略しない
  • RLSの設定漏れを防ぐ
  • Authorizationヘッダをログ出力しない
  • CORS設定は必要最小限にする
  • Cookieを使用する場合はSecure属性、HttpOnly属性、SameSite属性を適切に設定する

フロントエンドからブラウザ経由でAPIを呼び出す場合、CORS設定はCaddyまたはauth-serviceで制御する。

CORSは必要最小限のOriginのみ許可する。

禁止:

Access-Control-Allow-Origin: *

推奨:

Access-Control-Allow-Origin: https://app.example.com

認証情報付きリクエストを許可する場合は、許可Originを明示する。

許可するOrigin(フロントエンドURL)は、フロントエンド設計確定後に決定する。(判断不可。要確認。)


現時点では、AuthorizationヘッダによるBearer Token送信を基本とする。

Cookieを利用する場合は、以下を必須とする。

  • Secure
  • HttpOnly
  • SameSite=Lax または Strict
  • CSRF対策
  • セッション固定攻撃対策

Cookie利用の有無は、フロントエンド設計およびIdP連携方式に応じて決定する。


現時点ではOIDCのみを実装対象とする。

ただし、将来的にSAML IdPへ対応できるよう、IdP設定は provider_type により拡張可能とする。

将来のSAML対応時には、以下の追加実装を検討する。

  • SAML metadata管理
  • SAML Response検証
  • NameIDによるユーザー解決
  • SAML属性による組織・ユーザー補足情報取得
  • OIDC subjectとの統一的な外部ID管理

外部認証方式がOIDCであってもSAMLであっても、内部的には以下へ正規化する。

external_provider_type
external_issuer
external_subject
user_id
org_id
permissions

バックエンドサービスは、OIDC/SAMLの違いを意識しない。


OpenAPI yamlでは、各APIに必要なPermissionを拡張項目として記載することを推奨する。

例:

paths:
/uss/v1/feature-a/{resource}:
get:
summary: 一覧取得
x-permission: <resource>:read
responses:
'200':
description: OK
post:
summary: 作成
x-permission: <resource>:create
responses:
'201':
description: Created

実際のPermission定義はOpenAPI yaml確定後に決定する。(判断不可。要確認。)

auth-serviceは、OpenAPIまたは別途定義されたルート認可設定をもとにPermissionチェックを行う。


48. ルート認可設定ファイル方針(判断不可。要確認。)

Section titled “48. ルート認可設定ファイル方針(判断不可。要確認。)”

APIルートとPermissionの対応は、設定ファイルまたはDBで管理する。

設定ファイル例(パターン):

routes:
# 他社USS向け外部API
- method: GET
path: /uss/v1/feature-a/{resource}
permission: <resource>:read
- method: POST
path: /uss/v1/feature-a/{resource}
permission: <resource>:create
- method: PUT
path: /uss/v1/feature-a/{resource}/{id}
permission: <resource>:update
- method: DELETE
path: /uss/v1/feature-a/{resource}/{id}
permission: <resource>:delete
# UI向け外部API
- method: GET
path: /api/v1/feature-a/{resource}
permission: <resource>:read
- method: POST
path: /api/v1/feature-a/{resource}
permission: <resource>:create
- method: PUT
path: /api/v1/feature-a/{resource}/{id}
permission: <resource>:update
- method: DELETE
path: /api/v1/feature-a/{resource}/{id}
permission: <resource>:delete

実際のルート定義は、OpenAPI yaml 確定後に決定する。(判断不可。要確認。)

OpenAPIの x-permission を正とするか、別設定を正とするかはプロジェクトで決定する。

推奨は、OpenAPIの x-permission を正とし、auth-service起動時に読み込む方式である。