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

第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 を検証:
    • isshttps://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 図 → 設計の一気通貫を扱います。