【図解】OpenID Connectとは?IDトークンでサインインするしくみと、OAuth 2.0との違いを初心者向けに解説

OAuth 2.0 の記事で、サインイン(認証)は OpenID Connect が足すもの、と書きました。その後、「ID トークンがなぜ必要なのか」を説明した解説動画の書き起こしを読んで、その説明が正しいかを RFC と仕様で調べ直したことがありました。この記事では、そのときに整理したことも含めて、OpenID Connect でサインインするしくみを、仕様と Microsoft(Entra ID)のドキュメントで確かめながら、最初の準備から一歩ずつ図にしました。

この記事でわかること
  1. OpenID Connect は、OAuth 2.0 の上に「誰がサインインしたか」を足すしくみ
  2. アクセストークンだけでサインインにしてはいけない理由と、ID トークンの役目
  3. ID トークンに入るものと、アプリが確かめること(aud・nonce など)
  4. Entra ID で、Web アプリにサインインするまでの流れと設定
  5. サインアウトは、アプリと Entra ID の両方のセッションを終える

まずはたとえで:名札と部屋の鍵

会議に参加するとき、受付で身分証を見せると、名札と部屋の鍵を渡されるとします。名札には、この会議用で、今日だけ有効で、受付の印が押してあります。会議の主催者は名札を見て、「この人は誰か」が分かります。一方、部屋の鍵は、部屋に入るためのものです。鍵を確かめるのは部屋の側で、鍵には誰の鍵かは書いてありません。

参加者身分証受付(Entra ID)名札 = ID トークンこの会議用(aud)今日だけ(exp)受付の印(署名)部屋の鍵 = アクセストークン入れる部屋(scope)鍵を見るのは部屋(API)誰の鍵かは書いていない名札は会議の主催者(アプリ)が見る「この人は誰か」が分かる。でも名札では部屋には入れない。鍵は部屋(API)が見るOpenID Connect は、OAuth の「鍵」に、本人を示す「名札」を足す
図1 名札(ID トークン)と部屋の鍵(アクセストークン)

OAuth 2.0 が出すアクセストークンは、この部屋の鍵です。OpenID Connect(OIDC)は、そこに名札にあたる ID トークンを足します。Microsoft のドキュメントも、ID トークンを会議の名札にたとえて、「利用者が本人であることをアプリが確かめるための情報」と説明しています。

OAuth 2.0 との違い

OpenID Connect Core 1.0 の冒頭には、OpenID Connect は「OAuth 2.0 プロトコルの上に乗せた、シンプルな ID の層」と書かれています。OAuth 2.0 が答えるのは「このアプリに何をさせてよいか」(認可)で、OpenID Connect が答えるのは「誰がサインインしたか」(認証)です。

OpenID Connect(認証)誰がサインインしたか→ ID トークン(アプリあて)OAuth 2.0(認可)このアプリに何をさせてよいか→ アクセストークン(API あて)OAuth 2.0 の上に乗る「ID の層」OpenID Connect が足すものscope=openidOIDC の要求だという合図ID トークンサインインの結果。JWTnonce要求と ID トークンを結ぶ値UserInfo利用者の情報を返す窓口Discovery設定の公開(.well-known)流れは OAuth 2.0 の認可コードフローのまま。そこに ID トークンが加わる
図2 OAuth 2.0 と OpenID Connect の関係

流れそのものは、認可コードフローとほとんど同じです。違いは、要求の scope に openid を入れることと、そうすると応答に ID トークンが入ることです。ID トークンは JWT で、アプリ(RP)あてに発行され、OP(認可サーバ)の秘密鍵で署名されています。

アクセストークンだけでサインインにしてはいけない理由

OAuth 2.0 だけでも、アクセストークンが取れたら「サインインできた」とみなすアプリを作れてしまいます。しかし、アクセストークンはサインインの根拠になりません。

アクセストークンだけID トークン誰あてか?API あて。アプリは読まない前提誰あてか?aud = このアプリのクライアント ID今サインインしたのか?利用者がいなくても使える今サインインしたのか?iat・auth_time で、いつの認証か分かるこの要求への答えか?差し込まれても見分けられないこの要求への答えか?nonce = このアプリが送った値サインインの根拠には、アプリあてに署名された ID トークンを使う
図3 アクセストークンだけでは分からないこと、ID トークンで分かること
  • 誰あてか分からない:アクセストークンは API あてで、アプリは中身を読まない前提です(JWT の検証の記事)。そのトークンが自分のアプリのために出されたものか、アプリには判断できません。
  • 今サインインしたとは限らない:OAuth の解説(oauth.net)は、アクセストークンは利用者がいなくなった後も長く使えるもので、持っているだけではアプリに何も伝えない、と説明しています。アクセストークンが示すのは「いつか、誰かがこのアプリに API を使うことを任せた」ことまでで、「今この人がアプリの前にいて、サインインした」ことではありません。たとえば次のような場面です。
    • 月曜の朝に田中さんが一度サインインし、その後はブラウザを閉じて休暇に入ったとします。アプリはリフレッシュトークンで、田中さんがいない間もアクセストークンを取り直せます(Entra ID の既定では、使い続ければ90日)。水曜の夜中にアプリが持っている新しいアクセストークンは、田中さんがその時間にサインインしたことを示しません。
    • クライアントクレデンシャルのように、そもそも人がいなくてもアクセストークンは出ます。「アクセストークンがある=誰かがサインインした」とは言えません。
    もし「アクセストークンを持っている=サインイン済み」とみなすと、アプリは、その場にいない人をサインインした人として扱ってしまいます。ID トークンなら、OP がいつ利用者を認証したか(iat、頼めば auth_time)が入っていて、nonce で「このサインインの要求への答え」だと分かります。
  • 差し込まれても見分けられない:RFC 9700(OAuth 2.0 のセキュリティのベストプラクティス)は、盗まれたアクセストークンを正しいアプリに差し込み、ほかの人になりすます攻撃を挙げています。トークンが URL で返るインプリシットフローを例にしていて、OAuth だけではこれを見分ける方法がない、としています。そのうえで、OpenID Connect の ID トークン(中のアクセストークンのハッシュと nonce)で防げること、インプリシットフローを使わず認可コードフローを使うことを勧めています。

ID トークンなら、aud に自分のクライアント ID が入っているか、nonce が自分の送った値と同じかを確かめられます。ほかのアプリあてに出た ID トークンや、自分の要求とは関係のない ID トークンは、ここで弾けます。サインインの根拠には、アプリあてに署名された ID トークンを使う、というのが OpenID Connect の考え方です。

基本の形(OpenID Connect Core)

登場人物と窓口

OpenID Connect では、ID トークンを出す側を OP(OpenID Provider)、ID トークンを受け取って信頼する側のアプリを RP(Relying Party)と呼びます。Entra ID が OP、サインインさせたい Web アプリが RP です。

利用者(End-User)ブラウザで操作するRPアプリ(Relying Party)ID トークンを信頼する側OP(OpenID Provider)= Entra IDauthorizeサインイン・同意tokenコードを引き換えるuserinfo利用者の情報jwks_uri署名を確かめる鍵openid-configuration窓口の一覧(設定)OpenID Connect では、認可サーバを OP、アプリを RP と呼ぶ
図4 OpenID Connect の登場人物と窓口

OP の窓口の場所は、設定(Discovery)の文書にまとめて公開されています。アプリは、この文書から認可やトークンの窓口、公開鍵の場所(jwks_uri)を知ります。

認可コードフローに openid を足す

OpenID Connect Core の認可コードフローを、図にしました。アルファベットは、このあとの Entra ID の流れ(番号付き)と区別するためのものです。

利用者(ブラウザ)RP(アプリ)authorizetokenOP(OpenID Provider)(A) OP へ転送するscope=openid・state・nonce(B) 認証の要求GET /authorize(C) 認証と同意OP が利用者を確かめる(D) 認可コード + stateブラウザ経由で RP に届く(E) コードを引き換えるコード + アプリの証明(F) ID トークン + アクセストークン(G) ID トークンを確かめるiss・aud・exp・nonce・署名(H) サインイン完了sub で利用者を見分ける必要なら UserInfo で追加の情報をもらう認可コードフローの (A) に openid を足すと、(F) で ID トークンが届く
図5 OpenID Connect の基本の形(OpenID Connect Core の認可コードフロー)
パラメータ意味(OpenID Connect Core の 3.1.2.1)
scope必須。openid を必ず入れる。入っていないと OpenID Connect の要求にならない
response_type必須。認可コードフローでは code
client_id必須。アプリのクライアント ID
redirect_uri必須。OP に事前に登録した値と完全に一致させる
state推奨。要求と応答を結ぶ値。CSRF(なりすましの要求)対策によく使う
nonce任意(認可コードフロー)。アプリのセッションと ID トークンを結ぶ値。再利用の攻撃を防ぐ

(F) で ID トークンが返るのは、(A) で openid を頼んだときだけです。(E) では、機密クライアントならアプリの証明(シークレットや証明書)を付けます。ここまでは OAuth 2.0 の認可コードフローと同じです。

ID トークンに入るもの

OpenID Connect Core の2章が決めている、ID トークンのクレームです。

必ず入る(REQUIRED)iss発行者(OP)sub利用者の ID(OP の中で一意)audRP のクライアント ID を含むexp期限iat発行した時刻条件つきで入るnonce要求に入れたら必ず入るauth_timemax_age などのとき任意(OPTIONAL)acr・amr認証の強さ・方法azp認可された相手ID トークンは「誰が・いつ・どの OP で・どの RP のために」認証されたかを示す JWT
図6 ID トークンのクレーム(OpenID Connect Core の2章)
クレーム意味
iss必須。ID トークンを出した OP
sub必須。利用者の ID。その OP の中で一意で、使い回されない
aud必須。RP のクライアント ID を含む(ほかの値が入ることもある)
exp・iat必須。期限と、発行した時刻
nonce要求に nonce を入れたら、OP は必ず ID トークンに入れる
auth_time利用者が認証した時刻。max_age を指定したときなどは必須
acr・amr任意。認証の強さと、使った方法
azp任意。入っている場合は、その相手のクライアント ID

アプリが確かめること

OpenID Connect Core の 3.1.3.7 は、RP が ID トークンを受け取ったときに確かめることを決めています。考え方はJWT の検証の記事と同じで、あて先が「API」ではなく「アプリ自身」になります。

発行者iss が OP の issuer と完全に一致あて先aud に自分のクライアント ID がある署名OP の公開鍵で。既定は RS256期限今が exp より前nonce送った nonce と同じ認証の時刻・強さauth_time・acr(頼んだときは)トークンエンドポイントから TLS で直接受け取った ID トークンは、仕様上は署名の検証をTLS の確認で代えてもよい。ただし Microsoft は、ライブラリで確かめることを勧めている自分あてで、自分の要求への答えで、本物で、期限内のときだけ受け入れる
図7 ID トークンの確かめ方(OpenID Connect Core の 3.1.3.7)

あて先(aud)に自分のクライアント ID が入っていなければ、そのトークンは受け入れません。nonce を送ったなら、ID トークンの nonce が同じ値かも確かめます。署名の方式は、登録で別に決めていなければ RS256 が既定です。なお仕様では、トークンエンドポイントから TLS で直接受け取った ID トークンは、TLS の確認で署名の検証に代えてもよいとされています。ただ、Microsoft のドキュメントは、ID トークンは手で確かめず、ライブラリで確かめるよう勧めています。

補足:UserInfo エンドポイント

OpenID Connect には、アクセストークンで利用者の情報を返す UserInfo という窓口もあります。profile や email のスコープを頼むと、名前やメールアドレスを返します。Entra ID の場合、UserInfo は Microsoft Graph にあり、Microsoft のドキュメントは、ID トークンの情報は UserInfo の情報を含んでいるので、通信を減らすためにも ID トークンから情報を取るよう勧めています。

誰が何を持つか

ここからは Microsoft の場合です。Entra ID を OP にして、ASP.NET Core の Web アプリ(機密クライアント)にサインインする場面を例にします。

利用者(ブラウザ)アプリ(サーバ)Entra IDGraph(UserInfo)パスワードなどEntra ID にだけ入力Entra ID のセッションSSO に使う Cookieアプリのセッションサインイン後の Cookieクライアント IDaud と比べる値シークレットコードの引き換えに使うstate・nonce要求ごとに作って覚えるID トークン確かめたら中身を使う署名用の秘密鍵ID トークンに署名公開鍵・設定jwks_uri・Discovery利用者の情報認証して ID を出す利用者の情報名前・メールなどID トークンで足りる多くは呼ばなくてよいサインインの結果(ID トークン)はアプリが確かめ、アプリのセッションに変える
図8 誰が何を持つか(Entra ID と Web アプリの場合)
  • 利用者(ブラウザ):パスワードなどは Entra ID にだけ入力します。ブラウザには、Entra ID のサインインのセッション(ほかのアプリへのシングルサインオンに使う)と、サインインが終わったあとのアプリのセッションの Cookie ができます。
  • アプリ(サーバ):クライアント ID とシークレットを持ちます。サインインのたびに state と nonce を作って覚えておき、届いた ID トークンを確かめてから、中身を使います。
  • Entra ID:ID トークンに署名する秘密鍵を持ち、公開鍵と設定を公開しています。
  • Graph(UserInfo):利用者の情報を返しますが、多くの場合は ID トークンで足ります。

全体の流れ(Entra ID・Web アプリの場合)

図8のものが、どこで作られ、どこを通って、どこで使われるのかを、一歩ずつ並べます。番号は2つの図を通しての番号です。

準備(最初に1回):①〜⑧

管理者アプリ(Web アプリ)Entra ID① アプリを登録するWeb・リダイレクト URI(…/signin-oidc)② クライアントシークレットを作るCertificates & secrets③ シークレットの値画面に一度だけ表示④ サーバで操作:設定に書くTenantId・ClientId・CallbackPath・資格情報⑤ 起動時に設定を取る…/v2.0/.well-known/openid-configuration⑥ 設定(Discovery)issuer・各窓口・jwks_uri⑦ 公開鍵を取るGET jwks_uri⑧ 公開鍵の一覧秘密鍵は渡らないアプリは比べる相手をそろえるissuer・クライアント ID・公開鍵サインインの前に、アプリは「どの OP を、どの鍵で信じるか」をそろえる
図9 準備の流れ:アプリを登録してから、比べる相手をそろえるまで
手順誰が・どこで何をするか鍵(物)はどうなるか・使う鍵
①管理者・管理センターアプリを Web として登録し、リダイレクト URI(例:https://localhost:5001/signin-oidc)を書くクライアント ID が決まる(ID トークンの aud に入る値)
②管理者・管理センタークライアントシークレットを作るEntra ID が値を作り、照合用の情報を持つ
③管理センター → 管理者シークレットの値が、画面に一度だけ表示される秘密の値が管理者の手元を通る
④管理者・サーバで操作アプリの設定に、テナント ID・クライアント ID・コールバックのパス・資格情報を書く秘密の値がサーバに置かれる
⑤アプリ → Entra ID起動時に、OpenID Connect の設定(Discovery)を取りに行く―
⑥Entra ID → アプリ設定が返る。issuer・各窓口・jwks_uri が入っている期待する発行者が分かる
⑦⑧アプリ ⇄ Entra ID公開鍵の一覧を取り、キャッシュする公開鍵がアプリに届く。秘密鍵は渡らない

シークレットの代わりに証明書を使う場合の鍵の動きは、クライアントクレデンシャルの記事の図6と同じです。⑤〜⑧は、Microsoft.Identity.Web が自動で行います。

サインイン(利用者が来るたび):⑨〜㉑

利用者(ブラウザ)アプリ(Web アプリ)Entra IDブラウザ経由(⑨〜⑮)サーバから直接(⑯〜⑱)サインインの完了(⑲〜㉑)⑨ ページを開くサインインが要るページ⑩ state と nonce を作る一時的に覚えておく⑪ サインイン画面へ転送scope=openid profile・state・nonce⑫ GET /authorize⑬ サインイン・同意多要素認証など⑭ 認可コード + stateブラウザ経由(form_post)⑮ state を確かめる⑩で覚えた値と同じか⑯ POST /token認可コード + シークレット + code_verifier⑰ ID トークンを作るaud・nonce・sub・oidEntra ID の秘密鍵で署名⑱ ID トークン(+アクセストークン)⑲ ID トークンを確かめる署名(Entra ID の公開鍵)・issaud・exp・nonce(⑩と同じか)⑳ アプリのセッションを作るCookie を発行する㉑ ページを返す「○○さん」と表示ブラウザを通るのはコードだけ。ID トークンはサーバどうしで受け取り、確かめる
図10 サインインの流れ:ページを開いてから、アプリのセッションができるまで
手順誰が・どこで何をするか鍵(物)はどうなるか・使う鍵
⑨利用者 → アプリサインインが要るページを開く―
⑩アプリこの要求のための state と nonce を作り、覚えておく2つの値がアプリにできる
⑪アプリ → ブラウザscope=openid profile・state・nonce(と PKCE の code_challenge)を付けて、Entra ID のサインイン画面へ転送するstate と nonce がブラウザを通って Entra ID へ
⑫ブラウザ → Entra ID/authorize に要求を送る―
⑬利用者・Entra IDサインインし(多要素認証など)、同意するパスワードなどは Entra ID にだけ入る
⑭Entra ID → アプリ認可コードと state が、ブラウザ経由でアプリの /signin-oidc に届くコードが動く(ID トークンではない)
⑮アプリstate が⑩で覚えた値と同じかを確かめる―
⑯アプリ → Entra ID認可コード・シークレット・code_verifier(PKCE の値)を付けて、トークンを頼むシークレットを送る
⑰Entra IDaud(アプリのクライアント ID)・nonce・sub・oid などを入れた ID トークンを作るEntra ID の秘密鍵で署名
⑱Entra ID → アプリID トークン(API を呼ぶならアクセストークンも)を返すID トークンがアプリに届く
⑲アプリID トークンを確かめる。署名・iss・aud・exp・nonce(⑩と同じか)Entra ID の公開鍵で検証
⑳アプリアプリのセッションを作り、Cookie を発行するアプリのセッションの Cookie がブラウザへ
㉑アプリ → 利用者ページを返す(例:「○○さん」と表示)―

ブラウザを通るのは、⑪〜⑭の要求と認可コードだけです。ID トークンは⑯〜⑱でサーバどうしで受け取り、⑲で確かめます。⑳でアプリのセッションができたあとは、ページを開くたびに Entra ID に行く必要はありません。Microsoft のドキュメントは、Web アプリのセッションの長さを ID トークンの期限(既定は1時間)に合わせることが多い、と説明しています(セッションとトークンの記事も参照)。

サインアウト:❶〜❽

サインアウトの流れも、図10と区別するため黒丸(❶❷…)の番号で並べます。

利用者(ブラウザ)アプリEntra IDほかのアプリ(同じ人がサインイン中)❶ サインアウトを押す❷ アプリのセッションを消すCookie を消す❸ ログアウト画面へ転送post_logout_redirect_uri を付ける❹ GET /logout❺ セッションを終えるEntra ID 側のサインイン❻ フロントチャネルログアウト URL を呼ぶ❼ セッションを消して 200 を返す❽ 元のアプリに戻すpost_logout_redirect_uri へアプリのセッションと Entra ID のセッションの両方を終えて、はじめてサインアウト
図11 サインアウトの流れ
手順誰が・どこで何をするか図10との違い
❶❷利用者・アプリサインアウトを押す。アプリは自分のセッション(Cookie)を消す⑳で作ったものを消す
❸❹アプリ → ブラウザ → Entra IDpost_logout_redirect_uri を付けて、Entra ID のログアウトの窓口へ転送する⑪⑫の代わりに /logout
❺Entra IDEntra ID 側のサインインのセッションを終える⑬でできたセッションを終える
❻❼Entra ID → ほかのアプリフロントチャネルログアウト URL を呼ぶ。ほかのアプリはセッションを消し、200 を返す設定したアプリだけ
❽Entra ID → ブラウザpost_logout_redirect_uri へ戻す―

Microsoft のドキュメントは、サインアウトでは「Entra ID のログアウトの窓口へ転送する」ことと「アプリの Cookie を消すかセッションを終える」ことの両方を行うよう書いています。どちらかが抜けると、利用者はサインインしたままになり、次に使うときにサインインを求められないことがあります。

Microsoft(Entra ID)での手順

図9・図10の番号に沿って、実際の操作と設定を見ます。

アプリを登録する(図9の①〜③)

①では、Entra ID ›› App registrations ›› New registration でアプリを登録します。サインインできるアカウントの種類(自分の組織だけか、ほかの組織もか)を選び、リダイレクト URI のプラットフォームを Web にして、https://localhost:5001/signin-oidc のように入力します。概要の画面に出る「Application (client) ID」と「Directory (tenant) ID」が、④で使う値です。

②③のシークレットは、Certificates & secrets ›› New client secret で作ります(本番では証明書が勧められています)。Authentication の画面には「ID tokens(インプリシットとハイブリッドのフロー用)」というチェックもありますが、Microsoft のドキュメントは、認可コードフローが推奨で、このチェックはそれが必要な場合だけ入れるよう書いています。

アプリの設定に書く(図9の④〜⑧)

④では、appsettings.json に Entra ID の情報を書きます。

④ appsettings.json(Microsoft のドキュメントの例)

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "CallbackPath": "/signin-oidc"
  }
}

TenantId は、サインインできるアカウントを決めます。自分のテナント ID なら、その組織の人だけです。CallbackPath は⑭で認可コードが届く場所で、①で登録したリダイレクト URI と合わせます。シークレットや証明書は、このほかに設定します。

④ Program.cs(Microsoft のドキュメントの例)

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;

var builder = WebApplication.CreateBuilder(args);

// Add authentication
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(builder.Configuration, "AzureAd")
                .EnableTokenAcquisitionToCallDownstreamApi() // Optional: if calling APIs
                .AddInMemoryTokenCaches(); // For production, use distributed cache

// Add Razor Pages or MVC
builder.Services.AddRazorPages()
    .AddMicrosoftIdentityUI(); // Adds sign-in/sign-out UI

var app = builder.Build();

// Configure middleware
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication(); //  Add authentication middleware
app.UseAuthorization();

app.MapRazorPages();
app.MapControllers();

app.Run();

AddMicrosoftIdentityWebApp で、OpenID Connect のサインインが組み込まれます。⑤〜⑧の設定と公開鍵の取得、⑩〜⑲のやり取りと ID トークンの確かめ、⑳のセッションの Cookie は、このライブラリと ASP.NET Core が行います。API も呼ぶなら、例のように EnableTokenAcquisitionToCallDownstreamApi を付けます。

ページを守り、利用者の情報を使う(図10の⑨・⑳㉑)

⑨の「サインインが要るページ」は、[Authorize] 属性で決めます。サインインしていない利用者がこのページを開くと、⑩以降の流れが始まります。

⑨㉑ サインインが要るページ(Microsoft のドキュメントの例)

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize] //  Require authentication
public class IndexModel : PageModel
{
    public void OnGet()
    {
        var userName = User.Identity?.Name;
        var userEmail = User.FindFirst("preferred_username")?.Value;
    }
}

サインインのあとは、ID トークンのクレームを User から読めます。ただし、表示に使う値と、利用者を見分けるのに使う値は分けます。

確かめるaudアプリのクライアント IDissテナント(v2.0 は /v2.0)exp・nbf期限(既定1時間)nonce要求のときと同じか利用者を見分けるoid変わらない IDtidテナントの IDsubアプリごとに違う表示だけに使うname表示名(変わる)preferred_usernameユーザー名(変わる)利用者ごとにデータを持つときは、oid と tid(または sub)をキーにするメールアドレスやユーザー名は変わったり、別の人に使い回されたりすることがあるID トークンで「誰か」を知る。判断に使うのは変わらない ID
図12 Entra ID の ID トークンの主なクレーム
クレームEntra ID での中身と使い方
audアプリのクライアント ID。合わなければ受け入れない
nonce最初の要求に入れた値と同じ。違えば受け入れない
oid利用者の変わらない ID。同じテナントなら、アプリが違っても同じ値
subアプリごとに違う、変わらない ID
tidサインインしたテナントの ID
name表示名。変わることがあり、表示だけに使う(profile スコープで入る)
preferred_usernameユーザー名。変わることがあり、判断には使わない(profile スコープで入る)

Microsoft のドキュメントは、メールアドレスや UPN などは変わったり、ほかの人に使い回されたりすることがあるので、利用者を見分けるのに使わないよう書いています。利用者ごとにデータを持つときは、sub か oid(必要なら tid と組み合わせて)をキーにします。

サインアウトする(図11の❸❹)

Microsoft.Identity.Web.UI を使うと、サインアウトのリンクが用意されています。手で作る場合は、Discovery の設定にある end_session_endpoint(ログアウトの窓口)へ転送します。

❸❹ ログアウトの要求(Microsoft のドキュメントの例)

GET https://login.microsoftonline.com/common/oauth2/v2.0/logout?
post_logout_redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F

post_logout_redirect_uri は、サインアウトのあとに戻る URL で、アプリの登録にあるリダイレクト URI のどれかと一致している必要があります。ほかのアプリも一緒にサインアウトさせたいときは、それぞれのアプリの登録にフロントチャネルログアウト URL を設定します(❻❼)。

ID トークンでしてはいけないこと

してはいけないこと理由(Microsoft のドキュメント)
ID トークンで API を呼ぶID トークンはサインインの証拠で、API への許可の証拠はアクセストークン。ID トークンを認可に使わない
メールアドレスやユーザー名で利用者を見分ける変わったり、使い回されたりする。sub や oid を使う
確かめずに中身を使う機密クライアントは ID トークンを確かめる。手で書かずにライブラリを使う
ブラウザだけのアプリで、確かめたから安全と考える端末やブラウザを触れる人は確かめを飛ばせるので、SPA やネイティブアプリでは確かめてもあまり意味がない

うまくいかないとき

エラー意味確かめること
AADSTS50011リダイレクト URI が登録と合わないアプリの登録のリダイレクト URI と、CallbackPath(既定は /signin-oidc)
AADSTS700016アプリが見つからないappsettings.json の ClientId が、登録の Application (client) ID と同じか
response_type が許可されていないID トークンを認可の窓口から直接頼んだが、アプリで有効になっていない認可コードフローを使っているか。ハイブリッドフローが本当に要るときだけ「ID tokens」を有効にする
access_denied利用者が同意しなかった、またはキャンセルした同意しないと進めないことを、アプリの画面で伝える
Authority の設定エラーInstance か TenantId がない、または正しくないInstance が https://login.microsoftonline.com/ か。TenantId が正しいか

企業での例

場面やること押さえどころ
社内ポータルに Entra ID でサインインさせるWeb アプリとして登録し、Microsoft.Identity.Web でサインインを組み込むTenantId を自社のテナントにして、社外のアカウントを入れない
サインインした人の Outlook の予定も表示するID トークンに加えて、Graph のアクセストークンももらうID トークンは「誰か」、アクセストークンは「何をしてよいか」。役目を混ぜない
利用者ごとに設定を保存するoid と tid(または sub)をキーにするメールアドレスをキーにすると、名前の変更や再利用で取り違える
複数の社内アプリをまとめてサインアウトさせる各アプリにフロントチャネルログアウト URL を設定するアプリのセッションと Entra ID のセッションの両方を終える

まとめ

項目内容
どんなものOAuth 2.0 の上に乗る ID の層。「誰がサインインしたか」を ID トークンで伝える
OAuth 2.0 との違い流れは認可コードフローのまま。scope に openid を入れると ID トークンが届く
ID トークンが要る理由アクセストークンは API あてで、誰あてか・今サインインしたかが分からない。ID トークンはアプリあてで署名つき
確かめることiss・aud(自分のクライアント ID)・署名・exp・nonce。ライブラリに任せる
Entra ID の場合Web アプリとして登録し、Microsoft.Identity.Web にテナント ID・クライアント ID・コールバックのパスを設定する
使い方確かめたらアプリのセッションを作る。利用者のキーは oid・sub。表示名やメールは表示だけに使う
サインアウトアプリのセッションを消し、Entra ID のログアウトの窓口へ転送する

参考にした資料

この記事は、次の仕様と公式ドキュメントで内容を確かめて書きました。仕様や対応状況は変わることがあるので、最新の情報はリンク先で確かめてください。

あとがき

ID トークンが要る理由を調べ直してみて、大事なのは「アクセストークンはアプリあてではない」という一点なのだと分かりました。名札と鍵を分けて考えると、ID トークンで API を呼ばない、アクセストークンでサインインにしない、という決まりも自然に覚えられます。次の記事では、OpenID Connect と SAML、OAuth の違いを並べて整理するつもりです。

コメント