OAuthとOpenID Connect(OIDC)の違いを図解:「認可」と「認証」を正しく理解する

はじめに

「Googleでログイン」「GitHubでログイン」を実装するとき、よく「OAuth認証」という言葉が使われます。しかし厳密には、これは間違いです。

OAuth 2.0は「認可(Authorization)」のプロトコルであり、「認証(Authentication)」を行う仕様ではありません。認証を行うのは、OAuth 2.0の上に構築された OpenID Connect(OIDC) です。

この違いを曖昧にしたまま実装すると、セキュリティの脆弱性につながります。本記事では、両者の役割の違いをゼロから丁寧に解説します。

この記事で分かること

  • 「認証」と「認可」がなぜ別物なのか
  • OAuth 2.0が解決する問題と、できないこと
  • OIDCがOAuth 2.0に何を追加したのか
  • IDトークン・アクセストークン・リフレッシュトークンの使い分け
  • 「OAuthで認証」が危険な理由
  • Azure Entra IDでの具体的な使い方

1. 「認証」と「認可」は全く別の概念

1.1 それぞれの定義

概念英語意味
認証Authentication(AuthN)「あなたは誰か」を確認するログイン画面でID・パスワードを入力
認可Authorization(AuthZ)「何ができるか」を制御する管理者のみが設定画面にアクセス可能

日常のたとえで言うと:

認証 = 社員証の提示(あなたが誰かを証明)
認可 = 入館できるフロアの制限(何にアクセスできるかを制御)

1.2 なぜ混同されるのか

「Googleでログイン」は一見「Googleに認証してもらっている」ように見えます。しかし内部では:

  1. OAuth 2.0 でGoogleへのアクセス許可(認可)を取得
  2. OIDC でユーザーの身元情報(認証)を取得

という2つの処理が行われています。OIDCはOAuth 2.0を内包しているため、見た目上は区別がつきにくいのです。


2. OAuth 2.0 — 「認可」のプロトコル

2.1 OAuth 2.0が解決する問題

OAuth 2.0が登場する前、サードパーティアプリにGoogleやGitHubのデータを渡す方法は「IDとパスワードをアプリに教える」しかありませんでした。

【OAuth以前の問題】

ユーザー → サードパーティアプリ に ID/パスワードを教える
                  ↓
         Googleのデータを取得

これでは:

  • パスワードを盗まれるリスク
  • 全データへのアクセスを許可してしまう
  • パスワード変更まで権限を取り消せない

という問題がありました。

OAuth 2.0は「アクセストークン」という限定的な許可証を使うことで、パスワードを渡さずに特定のリソースへのアクセスだけを委任できるようにしました。

2.2 OAuth 2.0の登場人物

graph LR RO["👤 リソースオーナー
(ユーザー)"] Client["📱 クライアント
(サードパーティアプリ)"] AS["🔐 認可サーバー
(Google / Entra ID)"] RS["📁 リソースサーバー
(Google Drive API等)"] RO -->|アクセス許可| AS Client -->|認可リクエスト| AS AS -->|アクセストークン| Client Client -->|アクセストークンでAPIコール| RS RS -->|データ返却| Client
役割説明
リソースオーナーデータを持つユーザー本人Googleアカウントの所有者
クライアントアクセスを要求するアプリサードパーティの写真編集アプリ
認可サーバーアクセストークンを発行するサーバーGoogle OAuth 2.0サーバー
リソースサーバー保護されたデータを持つAPIサーバーGoogle Drive API

2.3 OAuth 2.0のフロー(Authorization Code)

sequenceDiagram actor User as ユーザー participant App as クライアントアプリ participant AS as 認可サーバー participant RS as リソースサーバー User->>App: ①「Googleと連携する」をクリック App->>AS: ②認可リクエスト
scope=drive.readonly AS->>User: ③同意画面表示
「このアプリにドライブの読み取りを許可しますか?」 User->>AS: ④同意 AS->>App: ⑤認可コード返却 App->>AS: ⑥認可コード → アクセストークンに交換 AS->>App: ⑦アクセストークン発行 App->>RS: ⑧アクセストークン付きでAPIコール RS->>App: ⑨データ返却

2.4 OAuthで分かること・分からないこと

ここが重要なポイントです。

OAuth 2.0 単体
分かることこのアクセストークンがドライブへのアクセス権を持っている
分からないことこのトークンを持っているユーザーは誰か

OAuth 2.0のアクセストークンは「リソースへのアクセスキー」であり、「ユーザーの身元証明」ではありません。


3. OAuthで「認証」しようとすると危険な理由

3.1 アクセストークンを「ログイン証明」に使う問題

OIDCを使わずにOAuthのアクセストークンやユーザー情報エンドポイントを「ログイン」に使うアプリがあります。これには脆弱性があります。

sequenceDiagram actor Attacker as 攻撃者 actor Victim as 被害者 participant MalApp as 悪意のあるアプリA participant TargetApp as 標的アプリB participant Google as Google OAuth Victim->>MalApp: ①悪意のあるアプリAにログイン MalApp->>Google: ②OAuthフロー実行 Google->>MalApp: ③アクセストークン取得 Note over MalApp: ④アクセストークンを窃取・保存 Attacker->>TargetApp: ⑤標的アプリBで
「Googleでログイン」を悪用 Note over Attacker,TargetApp: 盗んだトークンで
userinfo APIを叩く TargetApp->>Attacker: ⑥被害者としてログイン成功!

OIDCのIDトークンには aud(audience:誰向けのトークンか)クレームがあり、自分のアプリ向けでないIDトークンは拒否できます。アクセストークンにはこの仕組みがありません。

3.2 OAuthを「認証」に使ってはいけない

  • アクセストークンは「誰が持っているか」を保証しない
  • アクセストークンは別のアプリから横流しされても区別できない
  • 認証には必ずOIDCのIDトークンを使うのが正しい実装

4. OpenID Connect(OIDC) — OAuth 2.0に「認証」を追加

4.1 OIDCとは

OIDCはOAuth 2.0の上に認証レイヤーを追加した仕様です(2014年、OpenID Foundation策定)。

OIDC = OAuth 2.0(認可) + 認証レイヤー

具体的には、OAuth 2.0のフローに以下を追加しています:

追加要素内容
IDトークンユーザーの身元を証明するJWT
openid スコープOIDCフローを開始するスコープ
UserInfoエンドポイントIDトークン以外のユーザー属性を取得するAPI
ディスカバリーエンドポイントIdPの設定情報を自動取得する仕組み
nonceリプレイ攻撃を防ぐランダム値

4.2 OIDCのフロー(Authorization Code Flow)

sequenceDiagram actor User as ユーザー participant App as クライアントアプリ participant IdP as IdP
(認可サーバー) User->>App: ①ログインボタンをクリック App->>App: ②nonceを生成(セッションに保存) App->>IdP: ③認可リクエスト
scope=openid profile email
nonce=ランダム値 IdP->>User: ④ログイン画面 User->>IdP: ⑤ID/パスワード入力 IdP->>App: ⑥認可コード返却 App->>IdP: ⑦認可コード → トークンに交換 IdP->>App: ⑧IDトークン + アクセストークン
(+ リフレッシュトークン) App->>App: ⑨IDトークンを検証
・署名確認
・aud = 自分のclient_idか確認
・nonce一致確認
・有効期限確認 App->>User: ⑩ログイン完了

openid スコープを追加するだけでOIDCフローになり、IDトークンが発行されます。

4.3 IDトークンの構造

IDトークンはJWT(JSON Web Token)形式で、3つのパートに分かれています。

ヘッダー.ペイロード.署名
(Base64URLエンコード)

ペイロード(デコード後)の例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",
  "aud": "my-client-id.apps.googleusercontent.com",
  "exp": 1744030800,
  "iat": 1744027200,
  "nonce": "abc123xyz",
  "name": "山田 太郎",
  "email": "[email protected]",
  "picture": "https://lh3.googleusercontent.com/..."
}
クレーム意味用途
issIssuer(発行者)どのIdPが発行したか確認
subSubject(ユーザーID)ユーザーの一意識別子
audAudience(対象)自分のアプリ向けか確認(重要)
expExpiration(有効期限)トークンが期限切れでないか確認
iatIssued At(発行時刻)トークンの発行時刻
nonceリプレイ防止値リクエスト時のnonceと一致するか確認
name, emailユーザー属性UIへの表示など

5. 3種類のトークンの使い分け

OIDCフローでは最大3種類のトークンが発行されます。それぞれの役割を正しく理解することが重要です。

graph TD subgraph tokens["OIDCで発行されるトークン"] ID["🪪 IDトークン
(JWT)
─────────────
・ユーザーの身元証明
・アプリ内でのみ使用
・有効期限:短め(1時間程度)"] AT["🔑 アクセストークン
(JWT or 不透明)
─────────────
・APIアクセス権
・リソースサーバーに送る
・有効期限:短め(1時間程度)"] RT["🔄 リフレッシュトークン
(不透明)
─────────────
・新しいトークン取得用
・安全な場所に保管
・有効期限:長め(数日〜数週間)"] end ID -->|使用先| U1["アプリ内でのユーザー情報取得
ログインセッション管理"] AT -->|使用先| U2["APIコール時のAuthorizationヘッダー"] RT -->|使用先| U3["アクセストークン期限切れ時の再取得"]
トークン送信先保存場所有効期限
IDトークンアプリ内のみ(APIに送らない)メモリまたはセッション短い(〜1時間)
アクセストークンリソースサーバー(API)メモリまたはセッション短い(〜1時間)
リフレッシュトークン認可サーバーのみ安全なストレージ(HTTPOnly Cookie等)長い(日〜週単位)

よくある間違い

  • IDトークンをAPIの認証に送る → 誤り(APIにはアクセストークンを使う)
  • アクセストークンをユーザーの身元証明に使う → 誤り(認証にはIDトークンを使う)
  • リフレッシュトークンをlocalStorageに保存する → 危険(XSS攻撃で盗まれる)

6. OAuthとOIDCの対応関係

6.1 スコープによる動作の違い

OIDCはOAuth 2.0に openid スコープを追加するだけで有効になります。

リクエストのスコープ動作返却されるもの
scope=drive.readonlyOAuth 2.0のみアクセストークンのみ
scope=openidOIDC(認証のみ)IDトークン + アクセストークン
scope=openid profile emailOIDC(属性情報付き)IDトークン(名前・メール含む) + アクセストークン
scope=openid profile email drive.readonlyOIDC + OAuth 2.0IDトークン + Driveアクセス可能なアクセストークン

6.2 全体の位置づけ

graph TD subgraph outer["標準の重なり"] subgraph oauth["OAuth 2.0(認可フレームワーク)"] subgraph oidc["OpenID Connect(認証レイヤー)"] OIDC_core["IDトークン発行
UserInfoエンドポイント
ディスカバリー
nonce"] end OAuth_core["アクセストークン発行
リフレッシュトークン
認可コードフロー
スコープ管理"] end end

OIDCはOAuth 2.0の機能をすべて内包しています。 OIDCを使えば認証と認可の両方が実現できます。


7. Azure Entra IDでの実装例

7.1 OIDCでMicrosoftアカウントログインを実装する

Entra IDのOIDCエンドポイントへのリクエスト例:

# 認可リクエスト(ブラウザをリダイレクト)
https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize
  ?client_id=your-client-id
  &response_type=code          ← Authorization Code Flow
  &redirect_uri=https://yourapp.com/callback
  &scope=openid profile email  ← openidを必ず含める
  &state=ランダムな値(CSRF防止)
  &nonce=ランダムな値(リプレイ攻撃防止)

7.2 スコープの意味

スコープ取得できる情報
openidOIDCを有効化(subクレーム)
profilename, family_name, given_name 等
emailemail アドレス
offline_accessリフレッシュトークンの発行
User.ReadMicrosoft Graph経由でのユーザー情報

7.3 MSALを使った実装(JavaScript)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { PublicClientApplication } from "@azure/msal-browser";

const msalConfig = {
  auth: {
    clientId: "your-client-id",
    authority: "https://login.microsoftonline.com/your-tenant-id",
    redirectUri: "https://yourapp.com/callback",
  },
};

const msalInstance = new PublicClientApplication(msalConfig);

// ログインリクエスト(OIDCフロー)
const loginRequest = {
  scopes: ["openid", "profile", "email"], // openidが必須
};

// ポップアップでログイン
const response = await msalInstance.loginPopup(loginRequest);

// IDトークンからユーザー情報を取得
const account = response.account;
console.log(account.name);   // ユーザー名
console.log(account.username); // メールアドレス

8. まとめ:OAuthとOIDCの違い

項目OAuth 2.0OIDC
目的認可(アクセス権の委任)認証(ユーザーの身元確認)
発行トークンアクセストークンIDトークン + アクセストークン
ユーザー情報仕様に含まれないIDトークン / UserInfoエンドポイント
策定IETF(RFC 6749)OpenID Foundation
策定年2012年2014年
関係基盤プロトコルOAuth 2.0の拡張

判断基準

  • 「ユーザーが誰か」を知りたい → OIDC(IDトークンを使う)
  • 「外部サービスのAPIにアクセスしたい」→ OAuth 2.0(アクセストークンを使う)
  • 両方必要 → OIDC(OAuth 2.0を内包しているため)

最重要ポイント

  1. OAuth 2.0だけで「認証」を実装してはいけない
  2. IDトークンの aud クレームを必ず検証する
  3. nonce を使ってリプレイ攻撃を防ぐ
  4. リフレッシュトークンはHTTPOnly Cookieなど安全な場所に保存する

参考リソース