メインコンテンツまでスキップ

第16章 OAuth と OpenID Connect: 外部サービスとの認証連携

この章で学ぶこと​

  • OAuth 2.0 の 4 役割 (Resource Owner / Client / Authorization Server / Resource Server) を区別できる
  • Authorization Code Flow + PKCE が現代の標準であることを理解する
  • ID Token (JWT) の構造 (header.payload.signature) と必須 claim を説明できる
  • ソーシャルログインの DB 設計ができる
  • Implicit Flow が非推奨化された経緯を理解する
前提知識

第 15 章 認証・認可システムの設計 を理解していること。OAuth は規格が複雑で、本章は実装の入り口に絞っています。

OAuth 2.0 とは何か​

OAuth 2.0 は 「自分のサービスのデータを別のサービスに使わせる」 ための認可プロトコルです。よくあるシナリオ:

  • 「Google アカウントでログイン」 (Google が認証 → 自社サイトがその ID を受け取る)
  • 「自社アプリから Twitter API で投稿」 (自社アプリが Twitter のユーザーデータに代理アクセス)
  • 「Slack に GitHub の通知を流す」 (Slack が GitHub の Webhook を受け取る権限を持つ)
用語: OAuth と OpenID Connect (OIDC)
  • OAuth 2.0 は 認可 (この人は X にアクセスする権利があるか) のプロトコル
  • OpenID Connect (OIDC) は OAuth 2.0 の上に乗った 認証 (この人は誰か) の拡張

「Google でログイン」は実は OpenID Connect (= OAuth 2.0 で認可 + ID Token で本人確認)。両者は混同されやすいですが、別の問題を扱っています。

OAuth 2.0 の 4 役割​

役割例
Resource Owner (ユーザー)あなた (Google アカウントの持ち主)
Client (アプリ)あなたが使うサードパーティアプリ (写真編集アプリなど)
Authorization Server (認可サーバ)Google のログインサーバ
Resource Server (リソースサーバ)Google Drive API

「Resource Owner のデータに、Client が、Authorization Server の認可を経て、Resource Server からアクセスする」というのが OAuth の基本構図です。

Authorization Code Flow + PKCE (現代の標準)​

OAuth 2.0 にはいくつかの「フロー」(認可手順) がありますが、2026 年現在の標準は Authorization Code Flow + PKCE です (RFC 9700 / OAuth 2.1 draft)。

PKCE は public client で必須 (confidential でも推奨)​

OAuth のセキュリティ勧告 RFC 9700 (2025-01) は、Public Client (SPA / モバイル) に PKCE (Proof Key for Code Exchange) を必須 (MUST) としています。Confidential Client (サーバ側) にも推奨 (RECOMMENDED) で、OpenID Connect の confidential client は nonce パラメータで代替できます。さらに OAuth 2.1 draft では認可コードグラントの手順そのものに PKCE が組み込まれ、事実上の標準装備になりました。PKCE は元々 RFC 7636 (2015) で Public Client 向けに導入された仕組みです。

PKCE があると、悪意ある中間者が認可コードを盗んでも、code_verifier を知らないとトークンに交換できません。

Implicit Flow は使わない​

Implicit Flow は非推奨・削除されました

かつて SPA 向けに使われていた Implicit Flow は、RFC 9700 (2025-01) で非推奨となり、OAuth 2.1 draft では削除されました。古いチュートリアルや 2018 年以前の解説では Implicit Flow が紹介されていることがありますが、現代では SPA でも Authorization Code Flow + PKCE を使う のが正解です。

理由: Implicit Flow はトークンを URL の fragment で渡すため、ブラウザ履歴 / リファラ漏洩 / リダイレクト先のスクリプトに盗まれるリスクが高い。

OpenID Connect の ID Token (JWT)​

OpenID Connect では「この人は誰か」を表す ID Token が発行されます。ID Token の実体は JWT (JSON Web Token, RFC 7519) という標準フォーマットで、3 つの部分が . で区切られた文字列です。

eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTAxNjkxNTM1NTkzMjEzOTI3MzMiLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJleHAiOjE3MzAwMDAwMDAsImlhdCI6MTcyOTk5NjQwMH0.xxxxxxxxxxxxxxxxxx
└────── Header ──────┘ └─────────────── Payload ───────────────────────┘ └─ Signature ─┘

3 つの部分​

部分内容
Headerアルゴリズム (alg) と鍵 ID (kid)
Payloadclaim (ユーザーの情報)
SignatureHeader + Payload の暗号署名 (RS256 など)

必須 claim (OpenID Connect Core 1.0 規定)​

Claim意味
iss (issuer)発行者 (例: https://accounts.google.com)
sub (subject)ユーザーの一意 ID (発行者内でユニーク)
aud (audience)このトークンの宛先 (Client ID)
exp (expiration)有効期限 (Unix 時刻)
iat (issued at)発行時刻

検証手順 (Client 側)​

  1. 署名検証: 発行者の公開鍵 (/.well-known/openid-configuration から取得) で Signature を検証
  2. iss が想定の発行者か
  3. aud が自分の Client ID か
  4. exp が現在時刻より未来か
  5. nonce (認可リクエスト時に Client が送った 1 回限りのランダム値、リプレイ攻撃防止用) と一致するか

これらすべて OK のときだけ、payload の sub 等を信頼してユーザーを特定します。

自前で UUID + ハッシュ実装はしない

ID Token を自前実装で「UUID + SHA-256 ハッシュ」のように作ると、署名検証ができないためOpenID Connect 準拠ではありません。JWT ライブラリ (jose for Node.js、pyjwt for Python など) を使うのが標準です。

ソーシャルログインの DB 設計​

「Google でログイン」のような外部 IdP (Identity Provider) との連携を保存する DB 設計です。

ソーシャルログイン (PostgreSQL)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
display_name VARCHAR(100) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE user_oauth_providers (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider VARCHAR(30) NOT NULL, -- 'google' / 'github' / 'apple'
provider_user_id VARCHAR(255) NOT NULL, -- IdP 内の sub (ユーザーID)
access_token TEXT, -- 暗号化推奨
refresh_token TEXT, -- 暗号化推奨
token_expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_used_at TIMESTAMPTZ,
PRIMARY KEY (provider, provider_user_id), -- 1 IdP の 1 ユーザーは 1 行
UNIQUE (user_id, provider) -- 1 ユーザーは 1 IdP につき 1 連携
);

CREATE INDEX idx_oauth_user ON user_oauth_providers(user_id);

ポイント:

  • 主キーを (provider, provider_user_id) の複合にすると「同じ Google アカウントで複数ユーザーを作る」を防げる
  • access_token / refresh_token はアプリ層で暗号化してから保存 (鍵は別管理、KMS など)
  • 1 ユーザーが複数の IdP (Google + GitHub) を連携できる設計

トークンの保存と更新​

OAuth 2.0 のトークンには 2 種類あります。

トークン用途寿命
Access TokenAPI 呼び出し時に毎回送信短命 (典型的に 1 時間、IdP によって異なる)
Refresh TokenAccess Token が切れたら新しいものを取得長命 (数日〜数ヶ月、IdP によって異なる)
有効期限は IdP ごとに大きく違う

有効期限は OAuth 2.0 仕様 (RFC 6749) で定義されておらず、IdP の設定次第です。例:

  • Google: Access Token 1 時間 / Refresh Token 期限なし (使われている限り)
  • Facebook: Access Token 1-2 時間 / Long-lived Access Token 60 日
  • Microsoft: Access Token 1 時間 / Refresh Token 90 日

「業界標準は X 時間」と決まった値はないので、IdP のドキュメントを確認してください。

Refresh Token を使った更新​

-- アプリ層のロジック (Node.js 擬似コード)
async function getValidAccessToken(userId, provider) {
const row = await db.query(
'SELECT access_token, refresh_token, token_expires_at FROM user_oauth_providers WHERE user_id = $1 AND provider = $2',
[userId, provider]
);

// 5 分以内に期限切れなら refresh
if (row.token_expires_at <= new Date(Date.now() + 5 * 60 * 1000)) {
const newTokens = await refreshOauthToken(provider, row.refresh_token);
await db.query(
'UPDATE user_oauth_providers SET access_token = $1, refresh_token = $2, token_expires_at = $3 WHERE user_id = $4 AND provider = $5',
[encrypt(newTokens.access_token), encrypt(newTokens.refresh_token), newTokens.expires_at, userId, provider]
);
return newTokens.access_token;
}

return decrypt(row.access_token);
}

演習​

演習 1: フロー選択クイズ

次のシナリオに最適な OAuth フローを選んでください。

  1. ブラウザ上の SPA (React アプリ) で Google ログイン
  2. iOS ネイティブアプリで Spotify と連携
  3. サーバ間 (バックエンド ← API) で外部サービスを定期的に呼び出す
  4. 古いシステムで Implicit Flow を使っている
解答例
番号フロー理由
1. SPA で Google ログインAuthorization Code + PKCESPA は Public Client、PKCE で認可コード盗難を防ぐ
2. iOS ネイティブアプリAuthorization Code + PKCEネイティブアプリも Public Client。SDK を使うのが現実的
3. サーバ間連携Client Credentials Flowユーザーの代理でなくサーバ自身のアクセス。Authorization Code は使わない
4. 古い Implicit FlowAuthorization Code + PKCE に移行Implicit Flow は RFC 9700 で非推奨、移行計画を立てる

補足: Client Credentials Flow はユーザーが介在しないサーバ間連携用。例: バックエンドが Slack API でメッセージを投稿する。

ありがちな間違い:

  • 「JWT があれば認可サーバ不要」: JWT は単なるトークン形式で、認可サーバが発行する必要があるという基本は変わらない
  • 「SPA なら Implicit Flow で OK」: 古い情報。SPA でも 2026 年現在は Authorization Code + PKCE が正解
  • 「Client Credentials なら PKCE 不要」: Client Credentials Flow は認可コードが介在しないため PKCE 自体が適用外 (= 認可コード盗難リスクが存在しない)
演習 2: JWT 検証の手順

あなたが Client で、Google が発行した ID Token を受け取りました。検証手順を 5 ステップで答えてください。

解答例
  1. トークンを 3 つに分割: header.payload.signature の形式で . で分割
  2. 公開鍵を取得: https://accounts.google.com/.well-known/openid-configuration から jwks_uri を取得し、その JWKS から header.kid に対応する公開鍵を取り出す
  3. 署名を検証: header.alg (典型的に RS256) で header.payload の署名を公開鍵で検証
  4. claim を検証:
    • iss が https://accounts.google.com か
    • aud が自分の Client ID か
    • exp が現在時刻より未来か (exp > now)
    • iat が現在時刻に近いか (abs(iat - now) < 5min 程度のずれを許容)
    • (送信時に nonce を含めたなら) nonce が一致するか
  5. OK ならユーザー特定: payload.sub を「Google での一意 ID」として user_oauth_providers.provider_user_id と照合し、紐づく自社ユーザーを特定

実装ライブラリ (Node.js):

import * as jose from 'jose';

const JWKS = jose.createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs'));

const { payload } = await jose.jwtVerify(idToken, JWKS, {
issuer: 'https://accounts.google.com',
audience: process.env.GOOGLE_CLIENT_ID,
});

// payload.sub が Google でのユーザー ID
const user = await findUserByOauth('google', payload.sub);

ありがちな間違い:

  • 署名検証をスキップ: payload だけ base64 デコードして使うのは致命的。攻撃者が任意の payload を作れる
  • alg: none を許可: 古い JWT ライブラリの脆弱性。署名なし JWT を受け付けると改ざんし放題
  • 公開鍵をハードコード: Google は定期的に鍵を回転する。JWKS から動的取得

まとめ​

この章で学んだことを整理します。

  • OAuth 2.0 は 認可 (アクセス権) のプロトコル、OIDC は OAuth 2.0 上の 認証 (本人確認) 拡張
  • 現代の標準は Authorization Code Flow + PKCE (public client は RFC 9700 で必須、OAuth 2.1 では標準装備)
  • Implicit Flow は非推奨・削除 された (古いチュートリアルに注意)
  • ID Token は JWT (RFC 7519) 形式、必須 claim は iss / sub / aud / exp / iat
  • JWT 検証は「署名 → claim → ユーザー特定」の順、JWT ライブラリを使う
  • ソーシャルログインの DB は (provider, provider_user_id) を主キーに、トークンは暗号化保存

本ガイド第 3 部 (応用編) はここまでです。次の章からは実践編に入り、総合題材で要件 → E-R 図 → 設計の一気通貫を扱います。