はじめに: 「Google でログイン」の裏側で起きていること
どこかのサービスで 「Google でログイン」「GitHub で続行」「Sign in with Apple」 を押した経験は、誰にでもあるはずです。あの一瞬で、3つの当事者の間で精巧な舞踊が行われています。
この舞踊を支える標準仕様が OAuth 2.0 と OpenID 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 の登場人物
- Resource Owner(リソース所有者): ユーザー本人
- Client(クライアント): アクセスを欲しがるアプリ(例: 写真アップロードしたいサードパーティ)
- Authorization Server(認可サーバ): ユーザーの同意を取り、トークンを発行する(例: Google)
- 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)。仕組みは単純:
- クライアントが毎回ランダムな
code_verifierを生成 - そのハッシュ
code_challenge = SHA256(code_verifier)を認可サーバに送る - 認可コード受け取り時、トークン交換で
code_verifier生原文を渡す - 認可サーバ側で
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_token | id_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 のセキュリティ問題 / 公開鍵暗号の基本