SSO(シングルサインオン)入門 - 仕組み・プロトコル・実装パターン
SSO(Single Sign-On)の基本概念、主要プロトコル(SAML / OIDC / OAuth 2.0)の違い、認証フローの仕組み、実装時の注意点をまとめる。
SSOとは
SSO(Single Sign-On) は、一度の認証で複数のアプリケーションやサービスにアクセスできる仕組み。ユーザーはサービスごとにログインし直す必要がなくなる。
SSOの主なメリット
| メリット | 説明 |
|---|---|
| ユーザー体験の向上 | ログイン回数の削減、パスワード疲れの軽減 |
| セキュリティ強化 | パスワード使い回しの抑止、認証ポリシーの一元管理 |
| 運用コスト削減 | アカウント管理・パスワードリセット対応の集約 |
| コンプライアンス | アクセスログの一元管理、退職時の一括無効化 |
SSOの構成要素
- IdP(Identity Provider): 認証を担当するサービス。ユーザーの資格情報を管理し、認証結果を発行する
- 例: Microsoft Entra ID(旧Azure AD)、Okta、Google Workspace、Auth0
- SP(Service Provider)/ RP(Relying Party): IdPの認証結果を信頼してアクセスを許可するアプリケーション
- SAMLではSP、OIDCではRPと呼ぶ
- 認証トークン: IdPが発行する認証済みの証明。SAML AssertionやIDトークン(JWT)など
主要プロトコルの比較
SSOを実現するプロトコルは複数あるが、現在主に使われるのはSAML 2.0、OpenID Connect(OIDC)、OAuth 2.0の3つ。
| 項目 | SAML 2.0 | OpenID Connect (OIDC) | OAuth 2.0 |
|---|---|---|---|
| 主な用途 | エンタープライズSSO | Web/モバイルの認証 | APIの認可 |
| トークン形式 | XML(SAML Assertion) | JWT(IDトークン) | アクセストークン |
| 通信方式 | ブラウザリダイレクト(POST/Redirect) | ブラウザリダイレクト + バックチャネル | ブラウザリダイレクト + バックチャネル |
| ユースケース | 社内システム、SaaS連携 | コンシューマー向けWebアプリ、SPA | サードパーティAPIアクセス |
| 策定時期 | 2005年 | 2014年 | 2012年(RFC 6749) |
重要な区別: OAuth 2.0は「認可(Authorization)」のプロトコルであり、認証(Authentication)のプロトコルではない。OAuth 2.0の上に認証レイヤーを追加したものがOpenID Connect。
SAML 2.0
概要
SAML(Security Assertion Markup Language) はXMLベースの認証・認可プロトコル。エンタープライズ環境で広く使われている。
SP-Initiated SSOフロー
最も一般的なSAMLフロー。ユーザーがSP(アプリケーション)にアクセスするところから始まる。
sequenceDiagram
actor U as ユーザー
participant SP as SP(アプリ)
participant IdP as IdP
U->>SP: ① アクセス
SP-->>U: ② SAMLRequest(AuthnRequest)付きリダイレクト
U->>IdP: ③ SAMLRequest送信
IdP-->>U: ④ ログイン画面
U->>IdP: ⑤ 資格情報入力
IdP-->>U: ⑥ SAMLResponse(Assertion)付きリダイレクト
U->>SP: ⑦ SAMLResponse送信
SP-->>U: ⑧ アクセス許可
- ユーザーがSPにアクセス
- SPがSAML AuthnRequestを生成
- ユーザーのブラウザをIdPにリダイレクト
- IdPがログイン画面を表示(セッションがあればスキップ)
- ユーザーが認証情報を入力
- IdPがSAML Responseを生成(署名付きAssertion含む)
- ユーザーのブラウザをSPにリダイレクト(POSTバインディング)
- SPがAssertionを検証し、アクセスを許可
IdP-Initiated SSOフロー
IdP側のポータルからアプリケーションを起動するフロー。AuthnRequestを経由せず、IdPが直接SAML Responseを送信する。
セキュリティ上の理由(リプレイ攻撃への耐性が低い)から、可能な限りSP-Initiatedフローが推奨される。
SAML Assertionの構造
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
Version="2.0"
IssueInstant="2026-03-05T10:00:00Z">
<saml:Issuer>https://idp.example.com</saml:Issuer>
<ds:Signature>...</ds:Signature>
<!-- 認証ステートメント -->
<saml:AuthnStatement AuthnInstant="2026-03-05T10:00:00Z"
SessionIndex="_abc123">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<!-- 属性ステートメント -->
<saml:AttributeStatement>
<saml:Attribute Name="email">
<saml:AttributeValue>user@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="role">
<saml:AttributeValue>admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<!-- 条件(有効期限・対象SP) -->
<saml:Conditions NotBefore="2026-03-05T10:00:00Z"
NotOnOrAfter="2026-03-05T10:05:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://sp.example.com</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
</saml:Assertion>
SPでの検証ポイント
SPがSAML Responseを受け取った際に検証すべき項目:
- 署名の検証: IdPの公開鍵でXML署名を検証
- 発行者の確認:
Issuerが信頼済みIdPであること - 有効期限の確認:
NotBefore/NotOnOrAfterが現在時刻の範囲内であること - Audience制限:
AudienceRestrictionに自SPのエンティティIDが含まれること - InResponseTo: SP-Initiatedの場合、元のAuthnRequestのIDと一致すること
- リプレイ検出: 同じAssertionが再送されていないこと(AssertionIDの重複チェック)
OpenID Connect(OIDC)
概要
OpenID Connect はOAuth 2.0の上に構築された認証レイヤー。OAuth 2.0のアクセストークンに加えて、IDトークン(JWT) を発行することで「誰が認証されたか」を伝える。
用語の対応
| SAML | OIDC | 役割 |
|---|---|---|
| IdP | OP(OpenID Provider) | 認証を行う側 |
| SP | RP(Relying Party) | 認証結果を利用する側 |
| SAML Assertion | IDトークン(JWT) | 認証情報の伝達 |
Authorization Code Flow(推奨)
サーバーサイドアプリケーション向けの最も安全なフロー。
sequenceDiagram
actor U as ユーザー
participant RP as RP(アプリ)
participant OP as OP(IdP)
U->>RP: ① アクセス
RP-->>U: ② リダイレクト
U->>OP: ③ 認可リクエスト
OP-->>U: ④ ログイン画面
U->>OP: ⑤ 認証
OP-->>U: ⑥ 認可コード付きリダイレクト
U->>RP: ⑦ 認可コード
RP->>OP: ⑧ トークン要求(認可コード + client_secret)
OP-->>RP: ⑨ トークン応答(IDトークン + アクセストークン)
RP-->>U: ⑩ ログイン完了
認可コードはブラウザ経由で渡されるが、トークンの交換はサーバー間通信(バックチャネル)で行われる。これにより、トークンがブラウザに露出しない。
Authorization Code Flow with PKCE
SPA(Single Page Application)やモバイルアプリなど、client_secretを安全に保持できないクライアント向けの拡張。
// 1. code_verifier(ランダム文字列)を生成
const codeVerifier = generateRandomString(128)
// 2. code_challenge(code_verifierのSHA-256ハッシュ)を生成
const codeChallenge = base64urlEncode(sha256(codeVerifier))
// 3. 認可リクエストにcode_challengeを含める
const authUrl = new URL('https://op.example.com/authorize')
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('client_id', 'my-app')
authUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback')
authUrl.searchParams.set('scope', 'openid profile email')
authUrl.searchParams.set('code_challenge', codeChallenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
// 4. トークン交換時にcode_verifierを送信
const tokenResponse = await fetch('https://op.example.com/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: 'https://app.example.com/callback',
client_id: 'my-app',
code_verifier: codeVerifier,
}),
})
PKCEにより、認可コードが傍受されてもcode_verifierを知らなければトークンを取得できない。
IDトークン(JWT)の構造
IDトークンはJWT(JSON Web Token)形式で、ヘッダー・ペイロード・署名の3パートで構成される。
// ヘッダー
{
"alg": "RS256",
"kid": "key-id-001"
}
// ペイロード
{
"iss": "https://op.example.com",
"sub": "user-12345",
"aud": "my-app-client-id",
"exp": 1741176600,
"iat": 1741173000,
"nonce": "abc123",
"email": "user@example.com",
"name": "山田太郎"
}
主要なクレーム:
| クレーム | 説明 |
|---|---|
iss | トークン発行者(OPのURL) |
sub | ユーザーの一意識別子 |
aud | トークンの対象(RPのclient_id) |
exp | 有効期限(Unix timestamp) |
iat | 発行日時 |
nonce | リプレイ攻撃防止用の値 |
IDトークンの検証手順
- JWT署名の検証: OPのJWKS(JSON Web Key Set)エンドポイントから公開鍵を取得し、署名を検証
- issの確認: 信頼するOPのURLと一致すること
- audの確認: 自RPのclient_idが含まれること
- expの確認: 有効期限内であること
- nonceの確認: 認可リクエスト時に送信した値と一致すること(リプレイ攻撃防止)
OAuth 2.0との関係
OAuth 2.0の役割
OAuth 2.0は「認可」のフレームワーク。あるサービスのリソースに対して、サードパーティアプリにアクセス権を委譲する仕組み。
「Googleドライブのファイルを読み取る権限を、アプリXに与える」
→ これはOAuth 2.0(認可)
「このユーザーがGoogleアカウントの持ち主であることを確認する」
→ これはOpenID Connect(認証)
なぜOAuth 2.0だけでは認証に不十分か
OAuth 2.0のアクセストークンは「誰がログインしたか」を伝えるものではない。アクセストークンだけで認証を実装すると以下の問題が生じる:
- Audience制限がない: アクセストークンの対象者が検証できず、別のアプリ向けトークンの流用が可能
- 認証コンテキストがない: いつ・どのように認証されたかの情報がない
- 標準化されていない: ユーザー情報の取得方法がプロバイダーごとに異なる
OIDCはこれらをIDトークンで解決している。
セッション管理
SSOセッションとアプリケーションセッション
SSOでは2種類のセッションが存在する:
- IdPセッション: IdP側で管理するセッション。一度認証するとIdPにセッションが作られ、2回目以降のSP/RPからのリクエストではログイン画面をスキップできる
- アプリケーションセッション: 各SP/RP側で管理するセッション(Cookieベースが一般的)。IdPとは独立したライフタイムを持つ
シングルログアウト(SLO)
SSOの課題の一つが、1つのアプリケーションからログアウトした際に他のアプリケーションのセッションも終了させる シングルログアウト(SLO)。
| 方式 | 仕組み | 課題 |
|---|---|---|
| SAML SLO | IdPが各SPにLogoutRequestを送信 | 全SPへの通知が保証されない |
| OIDC Front-Channel Logout | ブラウザのiframeで各RPのログアウトURLを呼び出す | ブラウザのサードパーティCookie制限で動作しない場合がある |
| OIDC Back-Channel Logout | IdPがサーバー間通信で各RPにログアウトトークンを送信 | RPがエンドポイントを公開する必要がある |
| セッションタイムアウト | 一定時間後にセッションを自動失効 | 即座のログアウトではない |
実運用では、Back-Channel Logoutとセッションタイムアウトの組み合わせが現実的な選択肢となる。
実装時の注意点
セキュリティ
- HTTPS必須: 認証トークンの通信は必ずTLS上で行う
- state パラメータ: CSRF攻撃を防ぐため、認可リクエストにランダムな
stateを含め、コールバックで検証する - nonce パラメータ: IDトークンのリプレイ攻撃を防ぐため、OIDCでは
nonceを使用する - PKCE: パブリッククライアント(SPA/モバイル)では必ずPKCEを使用する。OAuth 2.1ではすべてのクライアントに対してPKCEが必須化される
- トークンの保管: アクセストークンやリフレッシュトークンはセキュアに保管する。SPAではメモリ内に保持し、localStorageへの保存は避ける
IdPの選定基準
| 基準 | 確認ポイント |
|---|---|
| 対応プロトコル | SAML / OIDC / OAuth 2.0のどれに対応しているか |
| MFA対応 | 多要素認証のサポート(TOTP、WebAuthn、SMS等) |
| ディレクトリ連携 | Active Directory / LDAPとの統合 |
| プロビジョニング | SCIM対応(ユーザーの自動作成・更新・削除) |
| カスタマイズ性 | ログイン画面、属性マッピング、認可ポリシー |
| 価格体系 | ユーザー数・SSO接続数による課金モデル |
SCIMによるプロビジョニング
SCIM(System for Cross-domain Identity Management) は、IdPとSP間でユーザー情報を自動同期するためのプロトコル。SSOと組み合わせることで、以下を実現する:
- ユーザーがIdPに追加されるとSP側にも自動作成
- IdPでの属性変更がSPに自動反映
- IdPでの無効化・削除がSPにも伝播(退職時の一括無効化)
プロトコル選定ガイド
flowchart TD
A[SSOプロトコルの選定] --> B{ユースケース}
B --> C["エンタープライズ<br/>社内/SaaS連携"]
B --> D["コンシューマー向け<br/>Webアプリ"]
B --> E["API連携のみ<br/>認証不要"]
C --> C1{対応状況}
C1 --> C2[相手がSAMLのみ対応] --> R1[SAML 2.0]
C1 --> C3[新規構築] --> R2[OIDC(推奨)]
C1 --> C4[既存AD環境] --> R3["SAML 2.0 or OIDC<br/>IdP次第"]
D --> D1{クライアント種別}
D1 --> D2[ソーシャルログイン] --> R4[OIDC]
D1 --> D3[SPA / モバイル] --> R5[OIDC + PKCE]
E --> R6[OAuth 2.0]
新規プロジェクトでは、特別な理由がない限り OIDC を第一選択とするのが現在の主流。SAMLはレガシーなSaaS連携で依然として必要になる場面がある。