ページの先頭です

ページ内を移動するためのリンク
本文へ (c)

ここから本文です。

OpenID Connect とは?【初心者向け】

簡単じゃない OpenID Connect を、図解でわかりやすくまとめました。

ライター:渥美 淳一
セキュリティアーキテクト。「ID で守り、ID を守る」がセキュリティの本質であり、未来への入り口だと考えている。

目次

OpenID Connect とは?

OpenID Connect は、HTTPS を使ったログインのしくみです。
オープンアイディーコネクト、略して OIDC といいます。

OpenID Connect は、ログインを OP(OpenID プロバイダー)にまかせます。

ユーザーは OP にログインします。
アプリはその結果を確認してユーザーをログイン(シングルサインオン)させます。

ログインの流れ

ユーザーはアプリにアクセスすると、下図の流れでアプリにログインできます。

① アプリはユーザーに「OP にログインして」と要求する
② ユーザーは OP にログインする
③ OP は引き換え券を発行する
④ アプリは引き換え券を OP に送る
⑤ OP はログイン結果をアプリに送る
⑥ アプリはログイン結果を確認してユーザーをログインさせる

引き換え券のことを「認可コード」といいます。
この流れが OpenID Connect でいちばん使われる認可コードフローです。

① アプリはユーザーに「OP にログインして」と要求する

アプリはアクセスしてきたユーザーを OP のログイン画面へ移動させます。
そのため、アプリは OP のログイン画面の URL を知る必要があります。

アプリは OP が公開する openid_configuration ファイル(例)を参照します。
OP の「認可」「トークン」「ユーザー情報」の URL が書かれています。

{
・・・ "authorization_endpoint": "https://op.com/authorize", "token_endpoint": "https://op.com/token", "userinfo_endpoint": "https://op.com/userinfo", ... }

認可の URL(authorization_endpoint)が OP のログイン画面の URL です。

アプリはユーザーを認可の URL へ移動させます。
このとき OP に「引き換え券」と「ログイン結果」がほしいと伝えます。

GET https://op.com/authorize?
response_type=code&
scope=openid
  • response_type=code
    「引き換え券がほしい」
  • scope=openid
    「ログイン結果がほしい」
    (アプリはほしい情報を scope で OP に伝えます)

② ユーザーは OP にログインする

OP のログイン画面が表示されます。
ユーザーはログインします。

③ OP は引き換え券を発行する

OP はユーザーをアプリ(リダイレクト URI)へ戻します。
リダイレクト URI は OP にあらかじめ登録しておきます。

このとき、引き換え券をアプリに渡します。

HTTP/1.1 302 Found
Location: https://app.com/callback?code=引き換え券

④ アプリは引き換え券を OP に送る

アプリは OP のトークンの URL(token_endpoint)に直接アクセスします。
もらった引き換え券を OP に送ります。

POST https://op.com/token
code=引き換え券 grant_type=authorization_code redirect_uri=https://app.com/callback
  • grant_type
    引き換え券(authorization_code)と引き換えにログイン結果をもらう
  • redirect_uri
    OP は前と同じアプリかを確認
    (別のアプリにすり替わっていないことを確認)

⑤ OP はログイン結果をアプリに送る

引き換え券と引き換えに、OP はアプリにログイン結果を返します。

その中には、ユーザー情報が入った ID トークンが含まれています。
アプリは ID トークンを見れば「誰がログインしたか」がわかります。

HTTP/1.1 200 OK
{ "id_token": "eyJ...", "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 }
  • access_token
    API にアクセスするためのトークン
    この後の⑦で OP からユーザー情報をもらいたいときに使う
  • token_type
    このアクセストークンの種類
    Bearer(ベアラー)は「持っている人が利用できる」トークン
  • expires_in
    このアクセストークンの有効期限
    3600 秒(1 時間)後に使えなくなる

⑥ アプリはログイン結果を確認してユーザーをログインさせる

アプリは ID トークンの中身を確認します。

まず「誰がログインしたか」を確認します。
さらに「この ID トークンが正しいか」を確認します。

そのために、アプリはまず ID トークンを デコード します。
ID トークンは JSON Web Token 形式(ヘッダー.ペイロード.署名)です。

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdHN1bWkiLCJpc3MiOiJodHRwczovL29wLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBwLmNvbSIsImlhdCI6MTcyOTk5MDAwMCwiZXhwIjoxNzMwMDAwMDAwfQ.署名

デコードされた ID トークンのペイロード例

{
  "sub": "atsumi",
  "iss": "https://op.com",
  "aud": "https://app.com",
  "iat": 1729990000,
  "exp": 1730000000
}
  • sub
    誰がログインしたか
  • iss
    どの OP が発行したか
  • aud
    どのアプリのためか
  • iat
    いつ発行されたか(日時を数字にしたもの)
  • exp
    いつまで使えるか(日時を数字にしたもの)


アプリはさらに署名も検証し、ID トークンが改ざんされていないか確認します。
署名の検証には OP の公開鍵が必要です。

アプリは OP が公開する openid_configuration ファイル(例)を参照します。
そして jwks_uri の URL にアクセスし、OP の公開鍵を取得します。

{
  ・・・
"jwks_uri": "https://op.com/keys",
... }

問題なければ、アプリはユーザーをログイン(シングルサインオン)させます。

⑦ アプリは OP から追加情報をもらう(必要な場合)

アプリは ID トークンを見て「誰がログインしたか」はわかりました。

ただ、「誰がログインしたか」だけでは足りず、プロフィールやメールアドレスなどの追加情報が必要な場合があります。

アプリは OP のユーザー情報の URL(userinfo_endpoint)にアクセスします。
⑤でもらったアクセストークンを OP に送ります。

GET https://op.com/userinfo
Authorization: Bearer アクセストークン

アプリは追加のユーザー情報をもらえます。

HTTP/1.1 200 OK
{ "sub": "atsumi", "name": "渥美 淳一", "email": "atsumi@example.com" }

ただし、プロフィールとメールアドレスをもらうには、①で scope=openid に加えて profile と email も指定します。指定しなかった情報はもらえません。

GET https://op.com/authorize?
response_type=code&
scope=openid profile email

ほかのフロー

OpenID Connect には、認可コードフロー以外のフローもあります。

  • インプリシットフロー(Implicit Flow)
    GET https://op.com/authorize?
    response_type=id_token&
    scope=openid
    ・インプリシットは「省略」
    ・OP は認可コード(引き換え券)の発行を省略する
    ・アプリはブラウザ経由で ID トークンをもらう
    ・ID トークンが漏えいするリスクがあるため推奨されていない
  • ハイブリッドフロー(Hybrid Flow)
    GET https://op.com/authorize?
    response_type=code id_token&
    scope=openid
    ・認可コードフローとインプリシットフローを組み合わせたフロー
    ・アプリはブラウザ経由で認可コードと ID トークンを同時にもらう
    ・アプリは認可コードと引き換えにアクセストークンをもらう
    ・ID トークンが漏えいするリスクがあるため推奨されていない

そのため、現在は認可コードフローが推奨されます。

セキュリティ

ここからは OpenID Connect のセキュリティです。

セキュリティのため、アプリは①のタイミングで確認用の値を送ります。
その後、「同じ値か?」を確認し、不正があれば中断します。

PKCE(Proof Key for Code Exchange)

OpenID Connect でいちばん使われる認可コードフローにも弱点があります。
攻撃者は認可コード(引き換え券)を盗めばトークンを取得できてしまいます。

PKCE は「認可コードを盗んでもトークンを取得できないしくみ」です。
(ピクシーと読みます)

アプリはまずランダム文字列をつくります。
次にランダム文字列のハッシュ値をつくって、①といっしょに OP に送ります。

GET https://op.com/authorize?
response_type=code&
scope=openid&
code_challenge=ランダム文字列のハッシュ値

ユーザーはログインし、OP は認可コードをアプリに渡します。
アプリはもらった認可コードと、最初につくったランダム文字列を OP に送ります。

POST https://op.com/token
grant_type=authorization_code code=認可コード code_verifier=ランダム文字列

この時点で、ランダム文字列のハッシュ値とランダム文字列が OP に届きました。

OP は、あとに届いたランダム文字列からハッシュ値をつくります。
最初に届いたランダム文字列のハッシュ値と同じならログイン結果を返します。

つまり、攻撃者は認可コードを盗んでも、ランダム文字列を知らないのでトークンを取得できません。PKCE は、現在では「必須」とされています。

state

アプリは①のタイミングで state=ランダムな値 をいっしょに送ります。

GET https://op.com/authorize?
response_type=code&
scope=openid&
state=sssssssssssssss

ユーザーはログインし、OP は認可コードといっしょに state をアプリに返します。

HTTP/1.1 302 Found
Location: https://app.com/callback?code=認可コード&state=sssssssssssssss

アプリは state を確認します。
自分が送った値と同じでなければ、アプリはそこで中断します。

nonce

アプリは①のタイミングで nonce=ランダムな値 をいっしょに送ります。

GET https://op.com/authorize?
response_type=code&
scope=openid&
nonce=nnnnnnnnnnnnnnn

OP は ID トークンに nonce を入れてアプリに返します。

{
  "sub": "atsumi",
  "iss": "https://op.com",
  "aud": "https://app.com",
  "iat": 1729990000,
  "exp": 1730000000,
  "nonce": "nnnnnnnnnnnnnnn"
}

アプリは nonce を確認します。
自分が送った値と同じでなければ、アプリはそこで中断します。

OpenID Connect セキュリティまとめ

PKCE 不正な認可コードなら中断
state 不正な応答なら中断
nonce 不正な ID トークンなら中断
ID トークンの署名 不正な ID トークンなら中断
redirect_uri 不正なアプリ(URL)なら中断

よくあるギモン

OAuth 2.0 との違いは?

OpenID Connect は OAuth 2.0 に「ログイン」と「ID トークン」を加えたものです。

OpenID Connect は scope=openid を指定します。
OP にログインすると、ID トークンをもらえます。

GET https://op.com/authorize?
response_type=code&
scope=openid

OAuth 2.0 では scope=openid を指定しません。
指定するのは scope=Calendars.Read(予定表の閲覧)などのアクセス権です。
認可サーバーからアクセストークンをもらえます。

GET https://op.com/authorize?
response_type=code&
scope=Calendars.Read

SAML との違いは?

SAML もログインのしくみですが、OpenID Connect とは以下の違いがあります。

  • アプリはログイン結果をブラウザ経由でもらう
    (OpenID Connect は OP から直接もらう)
  • ログイン結果は XML 形式でちょっと複雑
    (OpenID Connect は JSON 形式でわかりやすい)
  • ログイン結果をアサーションと呼ぶ
    (OpenID Connect は ID トークンと呼ぶ)
  • ログインするのは IdP、アプリは SP
    (OpenID Connect は OP と RP)
  • ブラウザからアクセスするアプリ向き
    (OpenID Connect はモバイルアプリや API にも対応)
  • 企業アプリでよく使われる
    (OpenID Connect は消費者アプリでも使われる)

このように、SAML は企業向け・ブラウザ中心のしくみです。
OpenID Connect はより幅広いアプリに対応するしくみです。

SPA と Web アプリの違いは?

SPA(シングル・ページ・アプリケーション)は、Google Maps のように同じ Web ページのまま地図を動かしたり場所を探したりできる、画面の切り替えをほとんど行わずに動作する Web アプリです。

Web アプリは、認可コードやトークンをサーバーで管理します。
いっぽう SPA は、認可コードやトークンをブラウザで管理します。
ブラウザはユーザーの端末上で動くため、セキュリティ対策が特に重要です。

以前の SPA は インプリシットフロー を利用していました。しかし認可コードが盗まれるリスクがあるため、現在は PKCE を使用した認可コードフロー が推奨されます。

AMR とは?

アプリは ID トークンを見て「誰がログインしたか」がわかります。
さらにアプリは AMR を見て「どうやってログインしたか」がわかります。
(Authentication Method Reference の略)

たとえばパスワードでログインしたら、OP は ID トークンに "pwd" を含めます。
"pwd" はパスワード認証を意味します。

{
  "sub": "atsumi",
  "iss": "https://op.com",
  "aud": "https://app.com",
  "iat": 1729990000,
  "exp": 1730000000,
  "amr": ["pwd"]
}

ここが "otp" なら、ワンタイムパスワード認証を意味します。

ここが "pop" なら、パスキーなどの秘密鍵を使った認証を意味します。
(Proof-of-possession of a key の略)

ACR とは?

アプリは ACR を見て「どのくらい強い認証でログインしたか」がわかります。
(Authentication Context Class Reference の略)

ACR は認証の強さです。
AMR は認証方法(どうやってログインしたか)です。

たとえばパスキーでログインしたら、OP は ID トークンに "phr" を含めます。
"phr" はフィッシングに強い認証を意味します。

{
  "sub": "atsumi",
  "iss": "https://op.com",
  "aud": "https://app.com",
  "iat": 1729990000,
  "exp": 1730000000,
  "acr": "phr",
  "amr": ["pop"]
}

アプリは①のタイミングで、認証の強さを OP に要求できます。

GET https://op.com/authorize?
response_type=code&
scope=openid&
acr_values=phr

または

GET https://op.com/authorize?
response_type=code&
scope=openid& claims={
"id_token": {
"acr": {
"essential": true,
"values": ["phr"]
}
}
}
  • "essential": true
    "phr" の認証が必須です
    (この要求を満たせなければ OP はエラーを返す)

CIBA とは?

CIBA (シーバ)は Client-Initiated Backchannel Authentication フローの略です。
これは、OpenID Connect の新しいログインのしくみです。

たとえば、妻がアプリで、夫のカードを使って購入しようとします。

このとき、アプリは OP に夫の認証(ログイン)を依頼します。
アプリは OP に「夫を特定するための情報」を送ります。

POST https://op.com/bc/authorize
scope=openid login_hint=夫を特定するための情報

OP はアプリに「受付番号」を返します。

HTTP/1.1 200 OK
{
  "auth_req_id": 受付番号,
  "expires_in": 受付番号の有効期限
}

OP は夫の認証デバイスを特定し、認証要求します。

その後、アプリはもらった受付番号を OP に送ります。

POST https://op.com/token
grant_type=urn:openid:params:grant-type:ciba auth_req_id=受付番号

夫の認証が成功すると、アプリは ID トークンをもらえます。

このように、別の認証デバイスで本人確認できるのが CIBA です。

Identity Assurance とは?

OpenID Connect は「誰がログインしたか」をアプリに伝えます。OpenID Connect for Identity Assurance は「その人は本人確認済みか」をアプリに伝えます。

アプリは、①で OP に本人確認済みの情報(verified_claims)を要求します。

GET https://op.com/authorize?
response_type=code&
scope=openid& claims={ "userinfo": { "verified_claims": { "essential": true } } }

アプリは OP から認可コードをもらい、それと引き換えに ID トークンやアクセストークンをもらいます。

アプリは OP のユーザー情報の URL(userinfo_endpoint)にアクセスします。
アクセストークンを OP に送ります。

GET https://op.com/userinfo
Authorization: Bearer アクセストークン

OP はアプリに「document によって本人確認済みの姓、名、生年月日」を送ります。
document とは、物理的な身分証明書(運転免許証やパスポートなど)を指します。

HTTP/1.1 200 OK
{ "sub": "atsumi", "verified_claims": { "verification": { "evidence": [ { "type": "document" } ] }, "claims": { "family_name": "渥美", "given_name": "淳一", "birthdate": "1990-01-01" } } }

Identity Assurance は、厳格な本人確認を必要とするアプリに貢献します。

※本記事の内容は執筆者個人の見解であり、所属する組織の見解を代表するものではありません。
組織・製品等の名称は執筆時点のものです。

RECOMMEND