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.0OpenID Connect (OIDC)OAuth 2.0
主な用途エンタープライズSSOWeb/モバイルの認証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: ⑧ アクセス許可
  1. ユーザーがSPにアクセス
  2. SPがSAML AuthnRequestを生成
  3. ユーザーのブラウザをIdPにリダイレクト
  4. IdPがログイン画面を表示(セッションがあればスキップ)
  5. ユーザーが認証情報を入力
  6. IdPがSAML Responseを生成(署名付きAssertion含む)
  7. ユーザーのブラウザをSPにリダイレクト(POSTバインディング)
  8. 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を受け取った際に検証すべき項目:

  1. 署名の検証: IdPの公開鍵でXML署名を検証
  2. 発行者の確認: Issuerが信頼済みIdPであること
  3. 有効期限の確認: NotBefore / NotOnOrAfter が現在時刻の範囲内であること
  4. Audience制限: AudienceRestriction に自SPのエンティティIDが含まれること
  5. InResponseTo: SP-Initiatedの場合、元のAuthnRequestのIDと一致すること
  6. リプレイ検出: 同じAssertionが再送されていないこと(AssertionIDの重複チェック)

OpenID Connect(OIDC)

概要

OpenID Connect はOAuth 2.0の上に構築された認証レイヤー。OAuth 2.0のアクセストークンに加えて、IDトークン(JWT) を発行することで「誰が認証されたか」を伝える。

用語の対応

SAMLOIDC役割
IdPOP(OpenID Provider)認証を行う側
SPRP(Relying Party)認証結果を利用する側
SAML AssertionIDトークン(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トークンの検証手順

  1. JWT署名の検証: OPのJWKS(JSON Web Key Set)エンドポイントから公開鍵を取得し、署名を検証
  2. issの確認: 信頼するOPのURLと一致すること
  3. audの確認: 自RPのclient_idが含まれること
  4. expの確認: 有効期限内であること
  5. 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種類のセッションが存在する:

  1. IdPセッション: IdP側で管理するセッション。一度認証するとIdPにセッションが作られ、2回目以降のSP/RPからのリクエストではログイン画面をスキップできる
  2. アプリケーションセッション: 各SP/RP側で管理するセッション(Cookieベースが一般的)。IdPとは独立したライフタイムを持つ

シングルログアウト(SLO)

SSOの課題の一つが、1つのアプリケーションからログアウトした際に他のアプリケーションのセッションも終了させる シングルログアウト(SLO)

方式仕組み課題
SAML SLOIdPが各SPにLogoutRequestを送信全SPへの通知が保証されない
OIDC Front-Channel Logoutブラウザのiframeで各RPのログアウトURLを呼び出すブラウザのサードパーティCookie制限で動作しない場合がある
OIDC Back-Channel LogoutIdPがサーバー間通信で各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連携で依然として必要になる場面がある。