ssecutils
Security / Browser-native guide

OAuth 2.0 と OpenID Connect 入門

認可と認証の違い、Authorization Code + PKCE、id_token と access_token の役割を説明します。

8Zero tracking reading surface

はじめに: 「Google でログイン」の裏側で起きていること

どこかのサービスで 「Google でログイン」「GitHub で続行」「Sign in with Apple」 を押した経験は、誰にでもあるはずです。あの一瞬で、3つの当事者の間で精巧な舞踊が行われています。

この舞踊を支える標準仕様が OAuth 2.0OpenID Connect (OIDC)。両者は別物ですが密接に関係していて、初学者が混乱する代表トピックです。この記事では「結局この2つは何が違うのか」「自前で実装する時に何を選ぶか」を整理します。

OAuth 2.0 と OpenID Connect の関係を一行で

  • OAuth 2.0: 認可(Authorization)の仕組み。「アプリAがユーザーの代わりに、サービスBの何かにアクセスする許可をもらう」
  • OpenID Connect: OAuth 2.0 の上に乗る認証(Authentication)の仕組み。「ユーザーが誰なのかを確認する」

日本語の「認可」と「認証」は紛らわしいですが、意味は明確:

用語英語問い
認証Authentication (AuthN)「あなたは誰?」
認可Authorization (AuthZ)「あなたに何の権限がある?」

OAuth 2.0 は本来「認可」のための仕様。なのに「Google でログイン」のような認証用途で広く使われていた歴史的経緯があり、それを正式に標準化したのが OpenID Connect です。

なぜ OAuth 2.0 が生まれたのか

OAuth 以前、サードパーティアプリからユーザーの Google アカウントにアクセスする場合、ユーザーが Google のパスワードをそのアプリに教えるしかありませんでした。

例えば「自分の Gmail のメールを別のメールクライアントに表示したい」時、メールクライアントに Gmail のパスワードを渡す必要があった。明らかに危険:

  • そのアプリが信頼できるかわからない
  • パスワードが漏れたらアカウント乗っ取り
  • スコープを限定できない(メール読むだけのつもりが Drive も Calendar も全部見られる)
  • 個別に取り消せない(アプリを使うのをやめるだけでは権限は消えない)

OAuth 2.0 の目的はこの「パスワードを渡さずに、限定的な権限を委任する」仕組みを作ること。「アクセストークン」という限定権限の鍵を発行する形に変えたのが画期的でした。

OAuth 2.0 の登場人物

  1. Resource Owner(リソース所有者): ユーザー本人
  2. Client(クライアント): アクセスを欲しがるアプリ(例: 写真アップロードしたいサードパーティ)
  3. Authorization Server(認可サーバ): ユーザーの同意を取り、トークンを発行する(例: Google)
  4. Resource Server(リソースサーバ): 守るべきAPI(例: Google Drive API)

多くの実装では Authorization Server と Resource Server は同じ事業者ですが、論理的には別の役割です。

Authorization Code Flow(最重要・現代の推奨)

OAuth 2.0 には複数のフロー(grant type)が定義されていますが、Web/SPA/モバイルアプリで使うべきはAuthorization Code Flowです。

全体の流れ:

1. ユーザーがクライアント上で「Google でログイン」を押す
   ↓
2. クライアントがブラウザを認可サーバへリダイレクト
   GET https://accounts.google.com/o/oauth2/auth
       ?client_id=APP_ID
       &redirect_uri=https://app.example.com/callback
       &response_type=code
       &scope=email profile
       &state=ランダム値
   ↓
3. 認可サーバ上でユーザーがログイン + 同意
   「app.example.com に email と profile を渡しますか?」
   ↓
4. 認可サーバが認可コード(code)付きで callback URL にリダイレクト
   https://app.example.com/callback?code=ABC123&state=ランダム値
   ↓
5. クライアント(サーバ側)が認可コードをトークンに交換
   POST https://oauth2.googleapis.com/token
     grant_type=authorization_code
     code=ABC123
     client_id=APP_ID
     client_secret=APP_SECRET    ← ここが重要
     redirect_uri=https://app.example.com/callback
   ↓
6. アクセストークン(と必要に応じてリフレッシュトークン)を取得
   { access_token: "...", refresh_token: "...", expires_in: 3600 }
   ↓
7. アクセストークンを使って API を叩く
   GET https://www.googleapis.com/oauth2/v3/userinfo
   Authorization: Bearer ...

2段階に分けるのには明確な理由があります:

  • 認可コード: ブラウザを経由するので、URL に出る = 漏洩リスクがある。短命(数十秒〜数分)で1回しか使えない
  • アクセストークン: サーバ間通信で取得するので URL に出ない。長命(1時間程度)で API 呼び出しに使える

client_secret はクライアントのバックエンドが持つ秘密で、認可コードをトークンに交換する時の本人確認に使います。SPA やモバイルアプリだと client_secret を安全に隠せない問題があり、これを解決するのが PKCE です。

PKCE - SPA/モバイルでの拡張

PKCE(Proof Key for Code Exchange、ピクシーと読む)は、「秘密を隠せないクライアント」のための拡張仕様(RFC 7636)。仕組みは単純:

  1. クライアントが毎回ランダムな code_verifier を生成
  2. そのハッシュ code_challenge = SHA256(code_verifier) を認可サーバに送る
  3. 認可コード受け取り時、トークン交換で code_verifier 生原文を渡す
  4. 認可サーバ側で SHA256(code_verifier) == code_challenge を検証

これで「認可コードを傍受しても、code_verifier を知らない攻撃者はトークンに交換できない」状態になります。2025年現在は SPA/モバイルだけでなく、すべての OAuth クライアントで PKCE 必須というのが OAuth 2.1 の方針です。

使ってはいけないフロー

Implicit Flow(廃止)

SPA向けに「ブラウザに直接アクセストークンを返す」シンプル版でしたが、URL fragment にトークンが出る、リフレッシュトークン取れない、リプレイ攻撃に弱い等の問題で廃止されました。代わりに Authorization Code + PKCE を使います。

Resource Owner Password Credentials Flow(廃止予定)

ユーザーのID・パスワードをアプリが直接受け取って認可サーバに送る方式。パスワードを渡さないという OAuth 本来の目的に反する。レガシー対応以外で使ってはいけません。

OpenID Connect(OIDC): OAuth に「あなたは誰」を加える

ここまで OAuth 2.0 は「認可(権限委任)」の仕組みでした。「Drive を読む権限をください」のような。

ところが現実には、多くのサービスが OAuth を「ログイン」目的で使っていました。「Google にログインできる ≒ そのGoogleアカウントの本人」という暗黙の前提で。これは規格的にはグレーで、実際にセキュリティ問題も起きていました(攻撃者がアクセストークンを奪うとログインできてしまう)。

この曖昧さを解消するのが OpenID Connect。OAuth 2.0 のフローに乗せて、「ID トークン」という認証用の追加トークンを発行します。

# scope に "openid" を含めるだけで OIDC モードになる
GET https://accounts.google.com/o/oauth2/auth
    ?client_id=...
    &response_type=code
    &scope=openid email profile  ← "openid" 必須
    &...

# トークンエンドポイントから2種類のトークンが返る
{
  "access_token": "...",     ← OAuth のアクセストークン(API呼び出し用)
  "id_token": "eyJhbGciOi...", ← OIDC の ID トークン(認証情報、JWT形式)
  "expires_in": 3600
}

ID トークンの中身(JWT Decoder で復号できます):

{
  "iss": "https://accounts.google.com",  // 発行者
  "sub": "10769150350006150715113",       // ユーザー固有ID
  "aud": "your-app-client-id",            // このトークンの宛先
  "exp": 1735689600,                      // 有効期限
  "iat": 1735686000,                      // 発行時刻
  "email": "user@example.com",
  "email_verified": true,
  "name": "Tanaka Taro",
  "picture": "https://..."
}

ID トークンは署名付き JWT。クライアントが署名を検証することで「この情報は本当に Google が発行したもの」と確信できます。JWT のセキュリティ問題 で書いた通り、署名検証を絶対サボらないのが鉄則。

access_token と id_token の使い分け

項目access_tokenid_token
目的API呼び出し(認可)ユーザー識別(認証)
送り先リソースサーバ(API)クライアント自身(ログイン処理)
形式不透明文字列も多い必ず JWT
署名検証クライアントは検証しないクライアントが必ず検証する
"ya29.A0ARrdaM...""eyJhbGci..."

「アクセストークンをログインに使う」は誤り。アクセストークンは「APIを叩く権利」であって「本人性の証明」ではありません。誰かのアクセストークンを奪った攻撃者でも有効に使えてしまうので、ログイン用途には id_token を使うのが OIDC の作法です。

UserInfo エンドポイント

ID トークンに含めない追加情報(メアド、プロフィール画像 URL 等)が欲しい時は、access_token を使って UserInfo エンドポイントを叩きます:

GET https://openidconnect.googleapis.com/v1/userinfo
Authorization: Bearer ya29.A0ARrdaM...

→ {
    "sub": "10769150350006150715113",
    "name": "Tanaka Taro",
    "email": "user@example.com",
    ...
  }

ID トークンを軽量に保ちつつ、必要に応じて詳細を取得する設計です。

セキュリティで必ず守ること

1. state パラメータで CSRF 対策

OAuth フロー中の state パラメータは、CSRF 対策。クライアントがランダム値を生成して認可サーバに送り、callback で同じ値が戻ってきたか確認することで、攻撃者が偽の callback リクエストを送り込む攻撃を防ぎます。

2. nonce で id_token のリプレイ対策

OIDC では nonce パラメータも使います。クライアントが生成 → 認可サーバに送信 → ID トークンに含めて返す → クライアントが検証。攻撃者が古い ID トークンを再利用する攻撃を防ぎます。

3. redirect_uri の完全一致

認可サーバに事前登録した redirect_uri と、リクエスト時の redirect_uri が完全一致することを認可サーバ側がチェックします。一致しないと redirect 自体されない。ワイルドカードや前方一致は事故の元

4. ID トークンの aud / iss / exp / 署名 検証

受け取った ID トークンは、何よりまず:

  • 署名(RSA/ECDSA の公開鍵で)
  • iss(発行者)が期待した認可サーバか
  • aud(対象)が自分の client_id か
  • exp(期限)が切れていないか
  • nonce が自分が発行したものと一致するか

を全て検証する必要があります。ライブラリに任せて、自前実装は避けるのが鉄則。

5. PKCE を使う

2025年以降、SPA/モバイルだけでなくサーバサイドでも PKCE は必須化の流れ。最初から有効化しておきましょう。

実装は自前で書かない

OAuth/OIDC の実装は「自前で書かない」が最優先のセキュリティ対策です。落とし穴が多すぎる:

  • state / nonce の検証漏れ
  • redirect_uri の検証漏れ
  • ID トークンの署名検証スキップ
  • PKCE 未対応
  • クッキーセキュリティ属性の設定漏れ

各言語の主要ライブラリ:

  • Node.js: openid-client, NextAuth.js / Auth.js
  • Python: authlib, django-allauth
  • Go: golang.org/x/oauth2 + go-oidc
  • Ruby: omniauth
  • Spring: spring-security-oauth2-client

さらにマネージド IDaaS(Auth0 / Clerk / WorkOS / Firebase Authentication / Cognito)を使えば、自前のコード量はほぼゼロにできます。

OAuth と OIDC のまとめ

最終的に覚えるべき要点:

  • OAuth 2.0 は認可(Authorization)の仕組み - 「APIアクセスの権限を委任する」
  • OIDC は認証(Authentication)の仕組み - 「ユーザーを識別する」
  • 使うフローは Authorization Code + PKCE(Implicit / Password Flow は廃止)
  • access_token は API 用、id_token はログイン用(用途を混ぜない)
  • state / nonce / PKCE / 署名検証は省略不可
  • 自前実装は避け、信頼できるライブラリを使う

おわりに

OAuth と OIDC は学習コストが高いですが、現代の Web 認証/認可の基盤プロトコルです。一度理解すれば、SaaS 連携、API 認証、SSO、社内ID基盤、すべてに応用できます。

本サイトの JWT Decoder で実際に Google や GitHub から取得した id_token を貼り付けると、claims(iss/sub/aud/exp/email等)の構造を可視化できます。OIDC を実装する時の理解の助けにしてください。

関連: セッション vs JWT 認証 / JWT のセキュリティ問題 / 公開鍵暗号の基本

Tool companion

この記事と一緒に使えるツール

Related reading

関連記事