🆔

OAuthとOpenID Connectの違いを整理

に公開

「OAuth認証」という言葉の曖昧さと、OpenID Connectが必要な理由

認証・認可周りの実装をする際、OAuthOpenID Connect (OIDC) の違いを厳密に意識できているでしょうか。
単に「Googleでログイン」を実装したい場合でも、裏側で動いているのがOAuthなのかOIDCなのかを理解していないと、セキュリティ上のリスクを招く可能性があります。

今回は、書籍『OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本』を読み、改めて整理した「認証と認可の違い」と「なぜOIDCが必要なのか」についてまとめます。

OAuthは「認可」の枠組みである

まず大前提として、OAuthは**認可(Authorization)**のためのプロトコルであり、認証(Authentication)のためのものではありません。

OAuthの本質は「リソースオーナー(ユーザー)が、クライアント(アプリ)に対して、リソースへのアクセス権限を委譲する」ことです。
例えば、「画像加工アプリが、Googleフォトにあるユーザーの写真を取得する」ケースを考えます。

  1. ユーザーは「このアプリに写真の読み取りを許可する」という意思表示をする
  2. アプリはアクセストークンを受け取る
  3. アプリはアクセストークンを使ってAPIから写真を読み込む

このフローにおいて、アプリは「ユーザーが誰であるか」を知る必要はありません。「許可証(アクセストークン)を持っている」という事実さえあれば機能するからです。

「OAuth認証」の問題点

しかし、実際にはOAuthの仕組みを使ってログイン(認証)を実現しているケースが多々あります。いわゆる「OAuth認証(Pseudo-Authentication)」と呼ばれる手法です。

これは、アクセストークンを使ってプロフィールAPI(/me など)を叩き、そこから返ってくるユーザー情報を元に「このユーザーだ」と特定する方法です。
一見正しく動いているように見えますが、あくまで「APIが叩けた結果、ユーザー情報が取れた」という副次的な結果を利用しているに過ぎず、プロトコルとして認証を保証しているわけではありません。

この手法には以下のようなリスクや欠点があります。

  • なりすましのリスク: 適切な署名検証がないため、トークンの置換攻撃などに対して脆弱になりやすい
  • 検証の欠如: クライアント側で「誰が」「いつ」発行したトークンかを暗号学的に検証する標準的な手順がOAuthには含まれていない

そこで OpenID Connect (OIDC)

この「認証」の欠落を埋めるために、OAuth 2.0を拡張して作られたのが OpenID Connect (OIDC) です。
ざっくり言えば、「OAuth 2.0 + IDトークン + UserInfoエンドポイント」 がOIDCの正体です。

OAuthとOIDCの比較

OIDCでは、従来のアクセストークンに加え、IDトークンというユーザーの身分証明書が発行されます。

IDトークンによる身元保証

IDトークンは JWT (JSON Web Token) 形式で記述されており、中には以下のような認証情報(クレーム)が含まれています。

  • iss: 発行者(Google, LINEなど)
  • sub: ユーザーを一意に識別するID
  • aud: どのアプリ向けに発行されたか
  • exp: 有効期限
  • 署名: 発行者の秘密鍵による署名

アクセストークンが単なる「ランダムな文字列(不透明なトークン)」であることが多いのに対し、IDトークンは中身が検証可能な構造化データである点が決定的に異なります。

実装におけるIDトークンの扱い

TypeScriptでIDトークンを扱う際のイメージは以下のようになります。
クライアントアプリは受け取ったIDトークンをデコード(および署名検証)することで、安全にユーザー情報を取得できます。

import { jwtDecode } from 'jwt-decode'

// IDトークンのペイロード定義
export interface DecodedJwt {
  sub: string       // ユーザー識別子
  name: string      // 表示名
  email: string     // メールアドレス
  exp: number       // 有効期限
  iss: string       // 発行者
  aud: string       // 対象クライアントID
}

// IDトークンからユーザー情報を抽出
const decodedJwt = jwtDecode<DecodedJwt>(idToken);

console.log(`User ID: ${decodedJwt.sub}`);

Discussion