🔄

OAuth 2.0とOpenID Connect(OIDC)

に公開

今回はこれまでのような私の専門分野であった「自然言語処理」や「人工知能」、「グラフ理論」の話ではなく。実際の業務で関わった中で興味深いと感じた技術をまとめた。ほとんど自分用の学習メモのようなものであり、この分野における浅学ゆえの間違いや不足があったら申し訳ないが、この記事(メモ)が誰かの役に少しでも立ってくれれば嬉しい限りである。

WebアプリケーションおよびAPI連携において、認証と認可の標準プロトコルとして「OAuth 2.0」と「OpenID Connect(OIDC)」というものが広く利用されている。両者は密接に関連するが、目的および設計思想は明確に異なる。本記事では、OAuth 2.0の概要を整理した上で、OIDCの設計意図と拡張点を明示し、両者の差異を論じる。

OAuth 2.0 — 権限委譲のためのフレームワーク

OAuth 2.0は、ユーザー(リソース所有者)の代わりに第三者アプリケーションがリソースにアクセスするための認可(Authorization)フレームワークである。設計目的は「パスワードを共有せずに、アクセス権限を安全に委譲する」ことである。

たとえば、外部のスケジュール管理アプリ(第三者アプリケーション)がGoogle Calendar API(リソース)にアクセスする場合、アプリはユーザーの認可を得てGoogleの認可サーバーからアクセストークンを取得する。アプリはこのトークンを用いてAPIを呼び出すが、ユーザーのパスワードを保持することはない。

OAuth 2.0は以下の4要素で構成される。

役割 名称 概要
Resource Owner リソース所有者(ユーザー) アクセスを許可する主体
Client クライアントアプリケーション リソースアクセスを希望するアプリ
Authorization Server 認可サーバー アクセストークンを発行するサーバー
Resource Server リソースサーバー 保護されたリソースを保持するサーバー

Authorization Code Flow

最も一般的なフローであるAuthorization Code Flowの手順は以下の通りである。

  1. クライアントが認可サーバーに認可リクエストを送信する
  2. ユーザーがログインおよびアクセス許可を与える
  3. 認可サーバーが「認可コード」をクライアントに発行する
  4. クライアントが認可コードをトークンエンドポイントに送信し、アクセストークンを取得する
  5. クライアントがアクセストークンを用いてリソースサーバーのAPIを呼び出す

このフローにおいて発行されるトークンはあくまでアクセス権限を表すものであり、ユーザー本人の「認証」を意味しない。

たとえば、上記の例を借り、外部のスケジュール管理アプリ(第三者アプリケーション)がGoogle Calendar API(リソースサーバー)にアクセスする場合を考える。このとき、アプリはユーザーのGoogleアカウントのパスワードを知ることなく、OAuth 2.0のフローを通じて「一時的なアクセス権(アクセストークン)」を取得し、安全にカレンダーデータへアクセスする。

  1. スケジュールアプリ → Google認可サーバー
    ユーザーが「Google連携」ボタンを押すと、アプリはGoogleの認可サーバー(https://accounts.google.com/o/oauth2/v2/auth)に認可リクエストを送信する。
    scope=https://www.googleapis.com/auth/calendar.readonly などを指定。

  2. ユーザー → Google認可サーバー
    Googleのログイン画面が表示され、ユーザーが自分のGoogleアカウントでログインする。さらに、「このアプリにカレンダーへの読み取り権限を与えますか?」という許可画面が表示され、ユーザーが同意を行う。

  3. Google認可サーバー → スケジュールアプリ
    同意後、Googleは認可コードをアプリにリダイレクト経由で返す。(例:https://example.com/callback?code=abc123

  4. スケジュールアプリ → Googleトークンエンドポイント
    アプリは受け取った認可コードを、Googleのトークンエンドポイント(https://oauth2.googleapis.com/token) に送信し、アクセストークンを取得する。

  5. スケジュールアプリ → Google Calendar API
    取得したアクセストークンをHTTPヘッダに付与して、GET https://www.googleapis.com/calendar/v3/calendars/primary/events を呼び出す。Google Calendar APIは認証済みのユーザーとしてイベント情報を返す。

このように、アプリはユーザーのパスワードに一切触れず、Googleの認可サーバーを介して安全にアクセストークンを取得する。これにより、ユーザーの資格情報を共有せずに外部サービス連携を実現できる点が、OAuth 2.0の大きな特徴である。

OAuth 2.0におけるScopeの意味

OAuth 2.0のscopeパラメータは、クライアントアプリがどの範囲のリソースにアクセスしたいかを宣言するための仕組みである。ユーザーはこれを通じて、「アプリにどのデータを利用させるか」を明確に制御できる。

たとえば、外部のスケジュールアプリがGoogle APIにアクセスする場合を考える。OAuthリクエストで指定するスコープの内容によって、許可される範囲が異なる。

スコープ 意味
https://www.googleapis.com/auth/calendar.readonly カレンダーの読み取りのみ許可
https://www.googleapis.com/auth/calendar.events カレンダーイベントの作成・編集を許可
https://www.googleapis.com/auth/contacts.readonly 連絡先情報の読み取りを許可

クライアントが認可サーバーにリクエストを送る際、これらのスコープをまとめて指定する:

GET https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=xxxx.apps.googleusercontent.com&
redirect_uri=https://example.com/callback&
scope=https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/contacts.readonly

このリクエストを受け取ったGoogleは、認可画面で次のようにユーザーに尋ねる:

このアプリに以下のアクセスを許可しますか?

  • Googleカレンダーの閲覧
  • Google連絡先の閲覧

ユーザーが許可を与えると、Googleはその範囲のアクセストークンを発行する。このトークンは、指定したスコープの範囲内でのみ有効である。

OAuth 2.0の限界

OAuth 2.0は本質的に認可の仕組みであり、認証を保証しない。すなわち、アクセストークンを保持していても「そのトークンの利用者が誰か」はわからない。これが、OAuth 2.0単体では「ログイン機能」として利用できない理由である。

たとえば、以下のような写真編集アプリとGoogleフォトの連携の例を考える。

  1. ユーザーは「PhotoMagic」という外部の写真編集アプリを使っている。このアプリから「Googleフォトの画像を読み込みたい」と考えたとする。
  2. PhotoMagicはGoogleの認可サーバーにリクエストを送り、ユーザーがGoogleフォトへのアクセスを許可すると、Googleはアクセストークンを発行する。
  3. PhotoMagicはこのトークンを使って、GoogleフォトのAPIから画像を取得できるようになる。

このとき、PhotoMagicは「有効なトークン」を受け取るが、そのトークンの利用者が「本人」かどうかは区別できない。 つまりPhotoMagicは、トークンを持ってしても本人確認、すなわちログインは行えないということである。

この問題を解決するために登場したのが、OpenID Connectである。


OpenID Connect(OIDC) — 認証の拡張仕様

OpenID Connect(OIDC)は、OAuth 2.0の認可コードフローを拡張し、認可(Authorization)認証(Authentication) を同時に実現する。OAuth 2.0のフローを再利用しつつ、 IDトークン(ID Token) という新たなトークン形式を導入することで、ユーザーの同一性を証明する仕組みを追加している。この仕組みにより、クライアン(Relying Party, RP)はユーザーの本人確認を行いつつ、APIアクセスのためのアクセストークンも取得できる。現代の「Googleログイン」「GitHubログイン」「LINEログイン」などのSSO(Single Sign-On)は、すべてOIDCを基盤として動作している。

OIDCでは以下の3者が主要な登場人物となる。

名称 役割
End User 認証されるユーザー
Relying Party (RP) 認証を利用するアプリケーション
OpenID Provider (OP) 認証を提供するプロバイダ(例:Google, Keycloak, Auth0など)

OIDCにおける認可コードフロー

OIDCの標準的なフローは以下の通りである。

  1. 認可リクエスト送信
    クライアントは認可サーバー(OpenID Provider, OP)にリクエストを送信する。リクエストには以下の情報が含まれる:
  • クライアントID
  • リダイレクトURI
  • response_type=code
  • scope=openid(必須)
  1. ユーザーの認証と同意
    認可サーバーはユーザーにログインを要求し、クライアントが要求するスコープ(たとえばprofileemail)へのアクセス同意を確認する。

  2. 認可コードの発行
    認可サーバーはユーザーが同意した後、認可コード(Authorization Code)をクライアントに返す。このコードは短時間のみ有効で、トークン取得に使用される。

  3. トークン要求と発行
    クライアントはトークンエンドポイントに認可コードを送信し、アクセストークンIDトークンを受け取る。IDトークンはJWT(JSON Web Token)形式で署名付きであり、sub(ユーザー識別子)などのクレームを含む。

  4. リソースアクセスおよび認証処理
    クライアントはアクセストークンを用いてリソースサーバーのAPIを呼び出す。同時に、IDトークンの署名と発行者情報を検証することで「誰がログインしているのか」を確認できる。

たとえば、「外部のスケジュール管理アプリ」が「Googleアカウントでログイン」を実現する場合、
アプリは以下のような手順でGoogleの認可サーバーと連携する。

  1. 認可リクエスト
    クライアント(スケジュール管理アプリ)は、Googleの認可サーバーにリクエストを送信する。リクエストには次のようなパラメータを含む:
  • client_id: 登録済みクライアントID
  • redirect_uri: 認可コードを受け取るコールバックURL
  • response_type=code: 認可コードを要求
  • scope=openid email profile: OIDCを有効化するためのスコープ
  1. ユーザー認証と同意
    認可サーバー(Google)はユーザーにログインを要求し、「このアプリがあなたのGoogleアカウント情報(メールアドレスなど)にアクセスしてもよいか?」という同意画面を表示する。

  2. 認可コードの発行
    ユーザーが同意すると、認可サーバーは認可コード(Authorization Code) をクライアントにリダイレクトURI経由で返す。

  3. トークン取得
    クライアントは、受け取った認可コードをGoogleのトークンエンドポイントへ送信し、アクセストークンとIDトークン(JWT形式) を取得する。このIDトークンには、署名付きでユーザーの識別情報(sub, email, nameなど)が含まれる。

  4. ユーザー認証の完了
    クライアントはIDトークンの署名を公開鍵で検証し、それが信頼できる発行者(Google)によるものであることを確認する。これにより、パスワードを一切扱わずに「誰がログインしたか」を安全に特定できる。

IDトークンの構造

IDトークンは JWT(JSON Web Token) 形式で表現される。以下は典型的な例である。

{
  "iss": "https://accounts.google.com",
  "sub": "1234567890",
  "aud": "your-client-id.apps.googleusercontent.com",
  "email": "user@example.com",
  "email_verified": true,
  "iat": 1610000000,
  "exp": 1610003600
}

このトークンは、署名付きで発行され、RPは公開鍵を用いて検証を行う。これにより、RPは「信頼できる発行者による正当なトークンである」ことを確認し、subemailなどのクレーム(Claim)からユーザーを特定する。

OIDCのスコープと要求パラメータ

OIDCのリクエストでは、OAuth 2.0と同様にscopeパラメータを使用する。OAuth 2.0ではscopeは、「どのデータにアクセスしたいか」を指定するが、OIDCではこれに「ユーザー情報を取得したい」という新しい目的が加わる。OIDCにおいてはopenidスコープを必ず含める必要がある。これがOIDCフローを有効化する鍵となる。

スコープ 説明
openid OIDCを有効化する必須スコープ(これがないとOAuth扱い)
profile 名前やアバターなどの基本プロフィール情報を要求
email メールアドレス情報を要求
address 住所情報を要求
phone 電話番号情報を要求

例えば、Googleログインを行う場合のOIDCリクエストだと;

GET https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=xxxx.apps.googleusercontent.com&
redirect_uri=https://example.com/callback&
scope=openid email profile

ここでopenidを含めることで、Googleの認可サーバーは**IDトークン(JWT形式)**を発行するようになる。これがOAuth 2.0とOIDCを分ける最も重要な違いである。scopeはOAuth 2.0でもOIDCでも共通して使われるが、OAuthではリソースアクセスの範囲指定、OIDCでは認証情報要求のトリガーとして働く。

PKCE(Proof Key for Code Exchange)によるセキュリティ強化

PKCE(RFC 7636)は、認証コードフロー(Authorization Code Flow)における認可コード盗難(Authorization Code Interception)攻撃を防止するために設計された拡張である。特に「クライアントシークレットを安全に保持できない」モバイルアプリやSPA(いわゆるパブリッククライアント)に対して必須の対策である。

クライアントシークレットとは

上記では詳しく触れなかったが、OAuth 2.0では、クライアント(アプリケーション)はクライアントシークレット(Client Secret)という秘密鍵のような情報を持っている。これは、トークンエンドポイントで「本当に正規のクライアントです」と認可サーバーに証明するためのもの(上記のフローのステップ4)で、外部に漏れてはいけない機密情報である。

OpenID Connect(OIDC)では、このクライアントシークレットがさらに重要になる。OIDCでは、アクセストークンに加えてIDトークン(ユーザーの本人性を表すトークン)を発行するが、このIDトークンを安全に受け取れるのは「正規のクライアント」だけでなければならない。したがって、OIDCにおいてもクライアントシークレットは認証とセキュリティの要となる。

しかし、モバイルアプリやSPA(Single Page Application)のように、アプリのコードや設定ファイルがユーザー端末上に存在する場合、このクライアントシークレットを安全に保持できない。誰でもアプリを解析すればシークレットを取り出せてしまうため、「クライアントが本物かどうか」をサーバー側で判別できなくなってしまう。

攻撃シナリオ

モバイルアプリやSPA(ブラウザ実行型アプリ)のように、クライアントシークレットを持たないクライアントの場合:

  1. ユーザーがブラウザでクライアント(アプリ)から認可リクエストを開始する。
  2. 認可サーバーはユーザーを認証し、認可コードをクライアントのredirect_uriへ返す(リダイレクト)。
  3. 正常ならクライアントはこの認可コードを受け取り、トークンエンドポイントへ送ってアクセストークンを取得する。

問題は、認可コードがリダイレクト経路やクライアントの内部で盗まれる状況である。

  • 悪意あるアプリが同一デバイスでリダイレクトURIを傍受する(カスタムスキームの競合など)。
  • ブラウザ拡張やフックコードがリダイレクトURLを横取りする。
  • 公開Wi-FiやプロキシでリダイレクトURIが中間者に露出する(HTTPでは致命的)。
    盗まれた認可コードを持つ攻撃者は、トークンエンドポイントへそのコードを送ればアクセストークンを取得できる。

PKCEによる防御原理

PKCEは「認可コード発行時にクライアントが生成する一時的秘密(code_verifier)を、認可コードと紐づけ、トークン取得時にその秘密を提示できることを要求する」ことで、盗聴者によるコードの再利用を防ぐ。

一般的なフロー:

  1. クライアントはランダム文字列 code_verifier を生成する(クライアントだけが知っている)。
  2. クライアントは code_verifier のハッシュ(通常は code_challenge = BASE64URL(SHA256(code_verifier))、方式は S256)を計算し、認可リクエストに code_challengecode_challenge_method=S256 を含める。
  3. 認可サーバーは code_challenge を受け取り、認可コードに紐づけて保持する(サーバー側で保存または紐づけ情報を保持)。
  4. 認可サーバーは認可コードを発行しクライアントに返す。
  5. クライアントはトークン要求時に元の code_verifier をトークンエンドポイントへ送る。
  6. 認可サーバーは受け取った code_verifier に対して同じハッシュを計算し、保存してある code_challenge と一致すればトークンを発行する。

攻撃者が認可コードを盗んでも、code_verifier を知らなければトークンを取得できない。従って、認可コードの窃取だけでは不正なトークン取得が不可能となる。


まとめ

  • OAuth 2.0は認可のためのフレームワークであり、認証を直接扱わない。
  • OpenID ConnectはOAuth 2.0を拡張し、IDトークンを介して認証を標準化する。
  • PKCEなどのセキュリティ機構を併用することで、安全なSPA・モバイル認証が可能となる。

GMOペパボ株式会社

Discussion