OIDC(OpenID Connect)の認証フローは、認可リクエストに載せるresponse_typeの値で決まります。仕様が定める組み合わせは6種類で、それぞれの分類先は認可コードフロー・インプリシットフロー・ハイブリッドフローのいずれかです。本記事では、3つのフローの違いをトークンの受け取り窓口で整理したうえで、フローごとに変わるnonce・c_hash・at_hashの検証、response_modeの既定値、IdPが実際に受け付ける組み合わせを確かめる手順を解説します。インプリシットから認可コード+PKCEへ移行するときは、既存システムに残してよい条件と新規採用しない場面を分け、その判断基準も最後に示します。
まとめ:OIDCの3つの認証フローで新規実装が選ぶべき組み合わせ
新規に作るログイン連携は、response_type=codeの認可コードフローにPKCEを付ける構成だけを選びます。サーバサイドWeb、SPA、モバイルアプリのどれであっても結論は変わりません。トークンはすべてトークンエンドポイントから受け取り、ブラウザのURLには短命の認可コードしか載せない形です。
インプリシットフロー(id_token/id_token token)は新規には採りません。OAuth 2.0のセキュリティBCPであるRFC 9700と、Microsoft Entraの公式ドキュメントがそろって使わないよう求めています。既存のSPAがインプリシットで動いているなら、移行の予定を立てる対象です。
ハイブリッドフロー(code id_tokenなど3種)は、認可応答の時点でIDトークンが要る既存システムに限って残してよい選択肢です。残すならc_hashの照合とform_postでの受け取りを必ずセットにします。
OIDCの認証フロー3種をresponse_typeの値で見分ける仕様の読み方
最初に、フロー名とパラメータ値の対応を仕様の原文で固定します。用語だけで覚えると、IdPの設定画面とリクエストの値が結び付きません。
Core 1.0のセクション3が定める6つのresponse_typeとフローの対応表
OpenID Connect Core 1.0(errata set 2を反映した版)のセクション3は、response_typeの値とフローを次のように対応させています。
| response_type | フロー | 認可応答に載るもの |
|---|---|---|
| code | 認可コード | 認可コードのみ |
| id_token | インプリシット | IDトークン |
| id_token token | インプリシット | IDトークンとアクセストークン |
| code id_token | ハイブリッド | 認可コードとIDトークン |
| code token | ハイブリッド | 認可コードとアクセストークン |
| code id_token token | ハイブリッド | 3つすべて |
OAuth 2.0のインプリシットグラントで使うtoken単独は、この表にありません。IDトークンが返らないため、OIDCとしてはログインを成立させられないからです。空白区切りの組み合わせという書き方そのものは、OAuth 2.0 Multiple Response Type Encoding Practicesが定義しています。値の並び順は意味を持たず、id_token codeと書いてもcode id_tokenと同じ扱いです。
トークンを返す窓口の違いで分かれる3フローの性質7項目の比較
3つのフローの差は、トークンを認可エンドポイントとトークンエンドポイントのどちらから受け取るかに集約されます。Core 1.0のセクション3にある比較表を、実装判断に効く順に並べ直しました。
| 性質 | 認可コード | インプリシット | ハイブリッド |
|---|---|---|---|
| トークンがブラウザに露出しない | はい | いいえ | いいえ |
| クライアントを認証できる | はい | いいえ | はい |
| リフレッシュトークンを得られる | はい | いいえ | はい |
| 全トークンを認可エンドポイントから | いいえ | はい | いいえ |
| 全トークンをトークンエンドポイントから | はい | いいえ | いいえ |
| 1往復で完結する | いいえ | はい | いいえ |
| 大半がサーバ間通信 | はい | いいえ | 場合による |
見るべきは上の3行です。インプリシットは「1往復で完結する」以外のすべてで不利になります。かつてはSPAがトークンエンドポイントへクロスオリジンで通信できないという事情があり、この1往復の利点が選ばれる理由でした。今はMicrosoft EntraのようにSPA向けの登録種別でトークンエンドポイントへのクロスオリジン通信を許すIdPがあり、その前提は崩れています。
response_modeの既定値とform_postでフラグメントを避ける受け取り方
認可応答をどこに載せて返すかはresponse_modeで決まります。指定しない場合の既定値はresponse_typeで変わり、codeはクエリ文字列、id_tokenまたはtokenを含む値はURLフラグメント(#以降)です。Core 1.0の3.2.2.5も、インプリシットの応答をフラグメントに載せると定めています。
フラグメントはサーバへ送られないため、受け取るにはブラウザ側のJavaScriptで読むしかありません。履歴やブラウザ拡張から見える位置にトークンが残る点も弱みです。そこで使うのがOAuth 2.0 Form Post Response Modeのresponse_mode=form_postで、IdPは自動送信するHTMLフォームを返し、応答値をフォームの本文としてredirect_uriへPOSTさせます。ハイブリッドやインプリシットを残すなら、受け取りはform_postに寄せてください。
認可コードフローでOIDCログインを組むときにOAuthへ足す処理
認可コードフローの手順そのものはOAuth 2.0と共通です。redirect_uriの登録やstate、コードの一回性は認可リクエストからトークン交換までの実装手順で扱っているので、ここではOIDCとして追加する差分だけを書きます。
openidスコープとnonceを付けた認可リクエストの組み立て例
OIDCのログインにするための条件は、scopeにopenidを含めることです。これが無いと認可サーバはIDトークンを発行せず、単なるOAuthの認可になります。認可コードフローでのnonceは仕様上は任意ですが、送っておけばIDトークンのリプレイ検出に使えます。
GET /authorize?response_type=code
&client_id=example-client
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&scope=openid%20profile%20email
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256 HTTP/1.1
Host: idp.example.com
code_challengeはRFC 7636が定めるPKCEのパラメータで、上の値はRFC 7636の付録Bにある計算例です。code_verifierの生成と保存先の設計はPKCEのcode_verifier実装と検証設計に分けています。
トークン応答のid_tokenを検証してからセッションを発行する順序
トークンエンドポイントの応答には、access_tokenと並んでid_tokenが入ります。ログイン判定に使うのはこのid_tokenで、アクセストークンをログインの証拠にしてはいけません。アクセストークンは「このAPIを呼んでよい」という許可であり、誰がどのクライアントにログインしたかを保証しないからです。
処理の順序は、署名検証、iss・aud・expの照合、送ったnonceとの一致確認を済ませてから、アプリのセッションを発行します。検証項目の全体はIDトークンの検証手順とOAuth 2.0との違いで解説しています。
インプリシットフローの仕組みとnonce必須・リフレッシュトークン不可の制約
インプリシットは、認可エンドポイントへの1回のリダイレクトでIDトークンまで受け取るフローです。速さの代償として、検証の負担はすべてクライアント側に乗ります。
id_tokenとid_token tokenの2種類で変わるat_hashの扱い
インプリシットではnonceが必須になります(Core 1.0の3.2.2.1と3.2.2.10)。IDトークンがブラウザ経由で届くため、盗まれたトークンの使い回しを、セッションに保存したnonceとの照合で止めるしかないからです。
id_token tokenでアクセストークンも受け取る場合、IDトークンにはat_hashが必須で入ります。at_hashはアクセストークンのハッシュ値の左半分で、IDトークンの署名がそのアクセストークンと対であることを示します。id_token単独ならアクセストークンが無いためat_hashも入りません。リフレッシュトークンはどちらでも発行されず、期限が切れたら再度リダイレクトさせる設計になります。
RFC 9700とMicrosoft Entraがインプリシットを使わないよう求める根拠
RFC 9700(Best Current Practice for OAuth 2.0 Security、2025年1月発行)の2.1.2は、クライアントはtokenや、認可応答でアクセストークンを返すその他のレスポンスタイプを使うべきでない(SHOULD NOT)と書いています。代わりに挙げているのはcodeと、トークンをトークン応答で返すcode id_tokenです。つまりid_token tokenとcode tokenは推奨から外れ、ハイブリッドのうちcode id_tokenだけが名指しで許容されています。
運用面の理由もあります。Microsoft Learnの暗黙的な許可のフローによれば、サードパーティCookieを削除するブラウザでは非表示iframeによるサイレントSSOが動きません。リフレッシュトークンも無いので、ユーザーは頻繁に再ログインさせられる。問題はまず使い勝手の崩れとして表に出ます。
ハイブリッドフローの3つの組み合わせとc_hash・at_hashの検証手順
ハイブリッドは、認可コードとIDトークン(またはアクセストークン)を認可応答で同時に受け取るフローです。検証の手順が最も多く、実装の抜けが起きやすい形でもあります。
code id_tokenで認可応答の時点にユーザーを特定する使い道
code id_tokenを使う理由は、トークン交換の前にユーザーを特定できる点にあります。認可応答に入ったIDトークンで「誰が戻ってきたか」を確定し、認可コードはバックエンドで交換してアクセストークンとリフレッシュトークンを得る。表示の分岐をトークン交換より先に決めたい画面や、OIDCの初期に作られたWebアプリで見かける構成です。
3種のうちnonceが必須なのはcode id_tokenとcode id_token tokenで、code tokenでは任意です(Core 1.0の3.3.2.1)。c_hashはcode id_tokenとcode id_token tokenで必須、at_hashはcode id_token tokenで必須になります(3.3.2.11)。
Node.jsによるc_hash・at_hash計算と認可コード差し替えの検出例
c_hashは、認可応答のcodeが、同じ応答のIDトークンと一緒に発行されたものかを確かめる値です。攻撃者が別のセッションで得た認可コードに差し替えても、IDトークンの署名は書き換えられないため、ハッシュが合わなくなります。計算方法はCore 1.0の3.3.2.11が定めており、IDトークンのalgに対応するハッシュ関数(RS256ならSHA-256)で値をハッシュし、左半分をbase64urlで符号化します。
// hybrid-hash.mjs(Node.js 18以降。外部パッケージ不要)
import { createHash } from 'node:crypto';
// IDトークンの alg からハッシュ長を決め、左半分を base64url にする
function halfHash(value, alg) {
const bits = alg.slice(2); // 'RS256' -> '256'、'ES384' -> '384'
if (!['256', '384', '512'].includes(bits)) {
throw new Error(`unsupported alg: ${alg}`);
}
const digest = createHash(`sha${bits}`).update(value, 'ascii').digest();
return digest.subarray(0, digest.length / 2).toString('base64url');
}
// 署名検証済みのクレームに対して c_hash と at_hash を照合する
export function verifyHybridResponse({ responseType, code, accessToken, claims, alg }) {
const types = new Set(responseType.split(' '));
if (types.has('id_token') && claims.c_hash !== halfHash(code, alg)) {
throw new Error('c_hash mismatch');
}
const atRequired = types.has('id_token') && types.has('token');
if (atRequired || (accessToken && claims.at_hash)) {
if (claims.at_hash !== halfHash(accessToken, alg)) {
throw new Error('at_hash mismatch');
}
}
}
// 動作確認:同じ入力なら毎回同じ22文字が出る
console.log(halfHash('SplxlOBeZQQYbYS6WxSbIA', 'RS256'));
呼び出す前提は、IDトークンの署名検証とiss・aud・nonceの照合が済んでいることです。署名を確かめていないクレームと比べても、攻撃者がc_hashごと作り直せるため意味がありません。
2枚のIDトークンでissとsubが一致するかを照合する実装の要点
code id_tokenとcode id_token tokenでは、IDトークンが2回届きます。1枚目は認可応答から、2枚目は認可コードを交換したトークン応答からです。Core 1.0は、この2枚でissとsubが同一でなければならないと定めています。
実装では、1枚目のsubをセッションに一時保存し、トークン交換後に2枚目と突き合わせます。食い違ったらログインを中断し、どちらの値も採用しません。プロフィール情報は2枚目から取り、本人の同定は両方の一致で行う、と役割を分けておきます。
IdPのDiscovery文書で対応フローを確認してから設計する手順
仕様が6種を定めていても、IdPがすべてを受け付けるとは限りません。設計の前に、使うIdPの実際の対応値を確認します。
GoogleとMicrosoft Entraの対応するレスポンスタイプ実測値の比較
OpenID Connect Discovery 1.0は、IdPが/.well-known/openid-configurationで対応機能を公開する形式を定めています。2026年11月5日時点で取得した値は次のとおりでした(項目名の末尾「_supported」は省略)。
| 項目 | Microsoft Entra(common) | |
|---|---|---|
| response_types | 8種(code token も含む) | 4種(code token は無し) |
| response_modes | query/fragment/form_post | query/fragment/form_post |
| PKCE方式の記載 | plain/S256 | 記載なし |
Googleはopenid-configurationでcode tokenやnoneまで8種を列挙しています。Microsoft Entraのcommonエンドポイントは4種で、code tokenとcode id_token tokenはありません。Entraではcode_challenge_methods_supportedが記載されていない一方、PKCEはドキュメント上で使えます。Discoveryの記載漏れを非対応と読み違えないよう、公式ドキュメントと照らし合わせてください。
curl・jqによるresponse_types・response_modes確認例
確認は1コマンドで済みます。IdPの選定段階で候補ごとに実行し、結果を設計書に残します。
curl -s https://accounts.google.com/.well-known/openid-configuration \
| jq '{response_types_supported, response_modes_supported, code_challenge_methods_supported}'
# 社内IdP(Keycloakなど)の場合は issuer の末尾に付ける
curl -s https://idp.example.com/realms/main/.well-known/openid-configuration \
| jq '.response_types_supported'
見るべきは、移行元で使っている組み合わせが残っているか、form_postが使えるか、S256が載っているかの3点です。
インプリシットから認可コード+PKCEへ移行する判断と見送る場面
ここからは判断の話です。結論として、インプリシットで動いている画面は移行の対象にし、ハイブリッドは条件を満たす間だけ残します。
SPAの移行で変わるredirect_uriの種別とトークン保存先の決め方
SPAを認可コード+PKCEに移すと、IdP側のアプリ登録が変わります。Microsoft Entraでは、リダイレクトURIを「Web」ではなく「シングルページアプリケーション」の種別で登録し直さないと、ブラウザからトークンエンドポイントを呼べません。アプリ側は認可応答からコードを取り出し、code_verifierを添えてトークンエンドポイントへPOSTする処理が増えます。
移行で迷うのはトークンの置き場所です。ブラウザ内に置くならメモリ保持を基本にし、localStorageは避けます。XSSが1つ通れば読み出せるためです。扱うデータが機微で、ログイン状態を長く保つ必要があるなら、BFF(Backend for Frontend)を置いてトークンをサーバ側に閉じ込める構成を選びます。社内の認証基盤ごと設計を見直す場合は、一創の認証基盤・ID管理システム開発でIdP選定から移行計画まで支援しています。
既存システムにハイブリッドを残してよい条件と新規採用しない場面の線引き
既存のハイブリッドを残してよいのは、次の条件をすべて満たす場合です。
response_typeがcode id_tokenで、アクセストークンを認可応答に含めていない- 受け取りが
form_postで、フラグメントにトークンが載らない nonce・c_hash・2枚のIDトークンのsub一致をすべて検証している- 使っているフレームワークの既定がハイブリッドで、変更するとミドルウェアの差し替えが要る
1つでも欠けるなら、残す理由より直す理由のほうが大きくなります。新規の画面では、たとえ同じフレームワークでもcode単独に設定を変えて作ってください。OAuth 2.1の草案(draft-ietf-oauth-v2-1、2026年10月31日更新の17版)はインプリシットを本文から外し、PKCEを全クライアントの前提としています。標準の向かう先に、ハイブリッドを新しく採る理由はありません。
移行レビューで差し戻す項目:nonce未検証とフラグメント受け取りの残存
移行のプルリクエストで見落としが多いのは、新しい処理を足して古い処理を消し忘れるパターンです。レビューでは次の3点を確認し、該当すれば差し戻します。
- コールバック処理に
location.hashを読むコードが残っている。インプリシット用の受け口が生きていれば、攻撃者はそちらへ誘導できる - 認可コードフローに移したことを理由に
nonceの照合を消している。任意になっただけで、送っているなら照合する - IdP側で暗黙的な許可(Entraなら「暗黙の付与およびハイブリッド フロー」のIDトークン・アクセストークン)が有効のまま残っている
3つ目はアプリのコードに現れないため、IdPの管理画面まで確認範囲に入れます。LINEログインのように、IdP固有のIDトークン検証APIを併用している場合の実装例はLINEログイン APIの実装手順:PKCE・IDトークン検証と既存会員IDの連携が参考になります。
よくある質問
OIDCの認証フローについて、実装の相談でよく受ける質問をまとめました。
OIDCとOAuth 2.0で認可コードフローの手順は違いますか?
手順の骨格は同じです。違いは、scopeにopenidを含めること、トークン応答にid_tokenが加わること、そのid_tokenを検証してからログインを成立させることの3点です。nonceは認可コードフローでは任意ですが、送れば照合に使えます。OAuth 2.0側のグラント種別の全体像はOAuth 2.0の仕組みと認可フローで整理しています。
インプリシットフローは今でも使えますか?
多くのIdPはまだ受け付けますが、新規には使いません。RFC 9700が認可応答でアクセストークンを返すレスポンスタイプを使わないよう求め、Microsoft Entraはサードパーティ Cookie を削除するブラウザでサイレントSSOが動かないと明記しています。リフレッシュトークンも得られないため、既存の画面も認可コード+PKCEへの移行を計画してください。
ハイブリッドフローはどんなときに使いますか?
トークン交換の前に、認可応答の時点でユーザーを特定したいときです。主にcode id_tokenを既定にしてきたフレームワークの既存システムで見かけます。使うならform_postで受け取り、nonceとc_hash、2枚のIDトークンのiss・sub一致を検証してください。新規の画面ではcode単独で足ります。
response_typeとgrant_typeは何が違いますか?
response_typeは認可エンドポイントへのリクエストで「何を返してほしいか」を指定する値です。grant_typeはトークンエンドポイントへのリクエストで「何と引き換えにトークンを出してほしいか」を示し、認可コードフローならauthorization_codeを送ります。前者はブラウザのリダイレクト、後者はサーバまたはアプリからのPOSTに載ります。
使うIdPがどのフローに対応しているかはどう確認しますか?
IdPの/.well-known/openid-configurationを取得し、response_types_supportedとresponse_modes_supportedを見ます。2026年11月時点ではGoogleが8種、Microsoft Entraのcommonエンドポイントが4種を列挙していました。記載が無い項目でも対応している場合があるため、PKCEのように重要な機能は公式ドキュメントでも確認してください。
関連記事
- JWTとは?構造・署名検証の仕組みとセッション・OAuth/OIDCとの違いを実装視点で解説:IDトークンの署名検証をライブラリ任せにせず理解したいときに。
- SAMLとは?認証フロー・IdP/SPの仕組みとOAuth・OIDCとの使い分けを実装視点で解説:社内SSOでSAMLとOIDCのどちらを採るか比べるときに。
- IDaaSとは?SSO・IdPとの違いとSAML・OIDC・SCIM連携を実装視点で解説:IdPを自前で持たず外部サービスへ寄せる判断に。
- 認証・ID管理とは?MFA・SSO・OIDC・IDaaSの違いと選定を解説【2026年版】:認証基盤の見直しを社内で説明する材料として。
- LINEログイン APIの実装手順:PKCE・IDトークン検証と既存会員IDの連携:ソーシャルログインで同じフローを実装する具体例として。