OAuthとは何か?― 既存の認証方式と比較して理解する「OAuthが解いている問題」
OAuthとは何か?
関連記事
- OAuth + OIDCを「自分で扱う」意味とセキュリティリスク― Webアプリ開発者とIdPプロバイダ、それぞれの視点から
- Webアプリのセッション管理ベストプラクティス― セッション固定攻撃・ログアウト設計・ストア選択
既存の認証方式と比較して理解する「OAuthが解いている問題」
Webアプリケーション開発において「認証」は避けて通れないテーマです。
Basic認証、セッション認証、APIキー認証、SAML……様々な手法が存在しますが、その中で OAuth は「少し特殊で分かりにくい」と感じられがちです。
この記事では、OAuthの仕様詳細に入る前に、
- OAuthは 他の認証方式と何が違うのか
- どのようなユースケースで 意味を持つ仕組みなのか
- 逆に OAuthが不要・不適なケース は何か
を、既存の認証方式との比較を通して整理します。
認証・認可の整理(超重要)
まず大前提として、OAuthを理解するには 認証(Authentication) と 認可(Authorization) を明確に分ける必要があります。
| 用語 | 意味 |
|---|---|
| 認証 | 「あなたは誰か?」を確認する |
| 認可 | 「あなたに何を許可するか?」を制御する |
多くの仕組みは 認証+認可を一体で扱う のに対し、
OAuthは 「認可」を中心に設計されたプロトコル である、という点が最重要ポイントです。
✅ OAuth = 認証の仕組み ではなく 認可の仕組み
(※ OpenID Connect は OAuth の上に乗った「認証」の仕組み)
OAuth以外の代表的な認証方式
OAuthを理解するために、まずは他の方式を整理します。
1〜3の方式には共通した本質があります:
アプリが秘密情報(資格情報)を特定の場所に格納し、それを毎回提示することで認証する
つまり、「正しい秘密情報を持っている=本人である」というモデルです。
1. Basic認証
本質
ユーザーの username:password という秘密情報を、リクエストのたびに Authorization ヘッダに載せてサーバーへ送る。
Authorization: Basic dXNlcjpwYXNzd29yZA==
↑ username:password をBase64エンコード
具体的な流れ:
- ユーザーがブラウザでアクセス
- ブラウザがID/パスワードのダイアログを表示
- 入力値をBase64エンコードしてリクエストに載せる
- サーバーがデコードして認証情報を検証
格納場所は「HTTPリクエストヘッダ」そのもの。リクエストのたびに毎回送信されます。
限界
- 資格情報を 毎回ネットワークに流す(HTTPSでないと丸見え)
- 権限の粒度がない(あるかないかの2択)
- 第三者への権限委譲は不可能
👉 「ユーザー本人が毎回直接アクセスする」という前提の方式
2. セッション認証(Cookie + Session)
本質
ログイン時に一度だけID/パスワードを検証し、サーバー側にセッション情報を格納する。以降はクライアントの Cookie に保存されたセッションIDを毎回送ることで認証する。
具体的な流れ:
- ユーザーがID/パスワードでログイン
- サーバーがDB等に
セッションID → ユーザー情報を保存 - セッションIDをCookieとしてブラウザへ返す
- 以降のリクエストでブラウザが自動的にCookieを送信
- サーバーがセッションIDを照合して認証
秘密情報(セッションID)の格納場所は「ブラウザのCookie」と「サーバーのセッションストア」の2箇所。
限界
- ブラウザ前提の設計なのでAPI連携・モバイルに不向き
- セッションストアをスケールさせる必要がある
- 第三者アプリに限定的な権限だけを渡すことができない
👉 「同一アプリ内で完結」する前提の方式
3. APIキー認証
本質
アプリケーションに固定の秘密情報(APIキー)を発行し、リクエストのヘッダやクエリパラメータに埋め込んで送信する。
具体的な流れ:
- 管理画面などでAPIキーを発行(例:
sk-abc123...) - クライアントがリクエスト時にキーを送信
Authorization: Bearer sk-abc123...
# または
GET /api/data?api_key=sk-abc123...
- サーバーがキーをDBと照合してアクセスを許可
秘密情報(APIキー)の格納場所は「クライアントの環境変数や設定ファイル」。サーバーtoサーバーの通信で使われることが多い。
限界
- APIキーはアプリケーション単位の認可であり、どのユーザーが操作しているかは識別できない
- キーが漏れると全権限が奪われる(スコープが弱い)
- ユーザーの同意・委譲という概念がない
👉 「アプリケーション単位の認可」に適した方式
まとめ:1〜3の共通モデル
| 方式 | 秘密情報 | 格納場所 |
|---|---|---|
| Basic認証 | ID/パスワード | HTTPリクエストヘッダ(毎回) |
| セッション認証 | セッションID | Cookie + サーバーのセッションストア |
| APIキー認証 | APIキー | クライアントの環境変数・設定ファイル |
共通の限界:「秘密情報を持っている者が本人」というモデルは、第三者への安全な権限委譲ができない。
4. SAML(Security Assertion Markup Language)
概要
SAMLは、エンタープライズ向けのシングルサインオン(SSO)を実現するXMLベースのプロトコルです。
「社内の複数のWebサービスに、IDシステムを一元化してログインする」ユースケースで広く使われています。
XMLベースの構造
SAMLの核心は SAMLアサーション と呼ばれるXML文書です。
IdP(Identity Provider)が「このユーザーは認証済みです」と署名して発行する証明書のようなもので、大まかにこんな構造をしています。
<!-- 誰が・誰に・いつまで有効か、ユーザー属性をXMLで表現 -->
<saml:Assertion ...>
<saml:Issuer>https://idp.example.com</saml:Issuer> <!-- 発行者 -->
<saml:Subject>
<saml:NameID>tanaka@example.com</saml:NameID> <!-- ユーザー -->
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>admin</saml:AttributeValue> <!-- 属性 -->
</saml:Attribute>
</saml:AttributeStatement>
<ds:Signature>...</ds:Signature> <!-- デジタル署名 -->
</saml:Assertion>
仕様の詳細は OASIS SAML 2.0 公式ドキュメント を参照してください。
SAMLアサーションの送信方法
SAMLアサーションは、主に ユーザーのブラウザ経由で SPへ届けられます。
- IdPがHTMLフォームにBase64エンコードしたアサーションを埋め込む
- ブラウザが自動的にそのフォームをSPへPOST送信する(HTTP POST Binding)
IdP → ブラウザ(HTMLフォーム) → SP(POST /acs)
アサーション自体はサーバー間で直接やり取りされるのではなく、ブラウザが仲介する点がSAMLの設計上の特徴であり、モバイルアプリとの相性が悪い理由でもあります。
SAMLのSSO全体フロー
SAMLには3つの登場人物がいます。
| 役割 | 英語 | 例 |
|---|---|---|
| ユーザー | User / Principal | 社員のブラウザ |
| サービスプロバイダ | SP (Service Provider) | SalesforceやSlack |
| IDプロバイダ | IdP (Identity Provider) | Okta、Azure AD |
[ユーザー] → [SP(Salesforce)] にアクセス
↓
[SP] 「うちは認証しない。IdPへ行って」→ SAMLリクエストを生成し、ユーザーをIdPへリダイレクト
↓
[ユーザー] → [IdP(Okta)] でログイン(社内のID/パスワード)
↓
[IdP] 認証成功 → SAMLアサーション(XML)を生成・署名
↓
[IdP] → ユーザーのブラウザ経由でSAMLアサーションをSPへPOST
↓
[SP] アサーションの署名を検証して、ユーザーのログインを許可
SAMLの特徴と限界
強み:
- デジタル署名によりアサーションの改ざんを検出できる
- エンタープライズで実績が豊富(Okta、Azure AD、Google Workspaceなど)
- 詳細なユーザー属性(ロール、部署など)を安全に渡せる
限界:
- XMLの解析が複雑で実装が重い
- モバイルアプリ・ネイティブアプリとの相性が悪い(ブラウザリダイレクト前提)
- REST APIとの親和性が低い(JSONではなくXML)
- セットアップが大がかり
👉 企業内ID統合・エンタープライズSSOの標準。モダンなWeb APIとは相性が悪い。
OAuthが登場した背景
1〜3の方式(Basic認証・セッション・APIキー認証)に共通する前提は、
「ユーザーとサービスが直接やり取りする」
という2者間の構図です。SAMLはIdPを介した3者構造を持ちますが、あくまで「ユーザーが自分のアカウントでサービスにログインする」という目的に特化しており、後述する問題には対応していません。
しかし、現実のプロダクトはこうなっています:
あなたはGoogleカレンダーのユーザーです。
myapp.comというスケジュール管理アプリが「Googleカレンダーを読み込みたい」と言っています。
この構図では 3者 が登場します。
| 登場人物 | 例 |
|---|---|
| ユーザー(あなた) | Googleアカウントを持っている人 |
| リソースを持つサービス | Google(カレンダーのデータを持っている) |
| 第三者アプリ |
myapp.com(あなたのデータにアクセスしたい) |
「第三者アプリ」とは、Googleとは別の会社・別のサービスのことです。
ここが1〜3の方式と根本的に異なる点で、これまでの方式は「あなたとGoogleの2者間」しか想定していませんでした。
問題は、myapp.com があなたのGoogleカレンダーにアクセスするには、あなたのGoogleパスワードが必要になってしまうことです。しかしパスワードをmyapp.comに渡すのは明らかに危険です。
- Googleカレンダーの予定は読めればいいのに、メールも削除できてしまう
- myapp.comがパスワードを漏洩させたら、Googleアカウント全体が乗っ取られる
- パスワードを変えたら、連携しているすべてのアプリの設定をやり直す必要がある
ここで初めて出てくる要求が、
✅ 第三者アプリに、限定的な権限だけを安全に委譲したい
これを解決するために設計されたのが OAuth 2.0 です。
OAuth 2.0:認可の委譲プロトコル
OAuthの核心
OAuthが提供する価値を一言で言うと:
「資格情報を共有せずに、権限だけを委譲する」
従来(NG): サードパーティアプリにID/パスワードを渡す → アプリがユーザーになりすまして操作
OAuth(OK): パスワードは一切渡さず、限定的なアクセストークンだけを渡す
OAuthの4つの登場人物
| 役割 | 説明 | 例 |
|---|---|---|
| Resource Owner | 権限を委譲するユーザー | あなた |
| Client | 権限を借りるアプリ | サードパーティアプリ |
| Authorization Server | トークンを発行するサーバー | Googleの認証サーバー |
| Resource Server | 実際のデータを持つサーバー | Google Calendar API |
実際のOAuthフロー:WebアプリでのGoogle認証
「Google でログイン」ボタンを押したときに、裏側で何が起きているかを追います。
[あなた] myapp.com で「Googleでログイン」をクリック
↓ ① myapp.com がブラウザをGoogleの認可画面へリダイレクト
[ブラウザ] → Googleのログイン・権限承認画面へ
※ここはGoogleのページ。myapp.comは関与しない
↓ ② あなたがGoogleアカウントでログイン&権限を承認
────────────────────────────────────────────
✅ Googleのパスワードを入力するのはGoogleのページ上
myapp.com にはパスワードが一切届かない
────────────────────────────────────────────
↓ ③ Googleが「認可コード」を発行し、myapp.com へリダイレクト
[ブラウザ] → https://myapp.com/callback?code=4/aBcD...
↓ ④ myapp.com のサーバーが認可コードをGoogleへ送り、
アクセストークンと交換(ブラウザは介在しない)
[myapp.com サーバー] ←→ [Google サーバー]
アクセストークンを取得
↓ ⑤ アクセストークンでGoogle APIを呼ぶ
[myapp.com サーバー] → Google Calendar API(許可された範囲のみ)
なぜパスワードが届かないのか:
myapp.com がリダイレクトするのは「Googleのページ」です。ユーザーはGoogleのページ上でパスワードを入力し、Googleが直接認証します。myapp.com に渡るのは「アクセストークン」だけ—これは カレンダーの読み取りのみ などスコープで制限された、期限付きの鍵です。
パスワードを渡す必要がないので、myapp.com がパスワードを盗んだり漏洩させたりするリスクがゼロになります。
ブラウザが開くのはなぜか:デスクトップアプリの場合
VS CodeやSlack、FigmaなどのデスクトップアプリでGitHubやGoogleのアカウント連携をすると、認証時にブラウザが開きます。これもOAuthの仕組みです。
なぜブラウザが開くのか?
デスクトップアプリ自体はGitHubのパスワードを扱えません(扱うべきではありません)。
ユーザーをGitHubのログインページに誘導するためにブラウザを起動します。
[デスクトップアプリ] 「Sign in with GitHub」をクリック
↓ システムブラウザを起動してGitHubの認可画面を開く
[ブラウザ] ユーザーがGitHubでログイン&承認
↓ GitHubが認可コードをカスタムURIスキームでアプリへ返す
[GitHub] → vscode://callback?code=... または myapp://callback?code=...
↓ OSがそのURIをデスクトップアプリに渡す
[デスクトップアプリ] 認可コードを受け取り、アクセストークンに交換
vscode:// や myapp:// のようなカスタムURIスキームを使うことで、ブラウザからデスクトップアプリへコードを受け渡しています。
これがOAuthの「ユーザーの操作を正規の認証サーバーに委ねる」という設計の本質です。
モバイルアプリの場合
モバイルアプリも基本的に同じ仕組みです。
[モバイルアプリ] 「Googleでログイン」をタップ
↓ システムブラウザ(またはアプリ内ブラウザ)でGoogleの認可画面を開く
[ブラウザ] ユーザーがGoogleでログイン&承認
↓ Googleが認可コードをカスタムURIスキームで返す
[Google] → com.myapp://callback?code=...
↓ OSがそのURIをモバイルアプリに渡す
[モバイルアプリ] アクセストークンに交換してAPI利用
モバイルアプリも、デスクトップアプリと同様にカスタムURIスキームでコールバックを受け取ります。またパブリッククライアント(client_secretを安全に保持できないモバイルアプリやSPAなど)では、認可コード横取り攻撃への対策として PKCE(Proof Key for Code Exchange)の使用が標準化されています。
OpenID Connect(OIDC):OAuthの上に乗る「認証」
OAuthだけでできること・できないこと
OAuthとOIDCの違いは、OAuth単体では何ができて、何ができないか を見ると分かりやすいです。
OAuth 2.0 だけの場合:
myapp.com が取得できるもの → アクセストークン(ya29.xxxxx)
これでできること:
✅ Google Calendar API を呼んでカレンダーデータを取得する
✅ Google Drive API を呼んでファイルを操作する
これができない:
❌ このトークンが「誰の」ものかを知る
❌ ユーザーをDBに登録・管理する(何を主キーにすればいい?)
❌ 「田中太郎としてログインした」と確認する
アクセストークンは「Googleに対してAPIを呼べる鍵」でしかなく、myapp.comにとってユーザーが誰かは分からないのです。
「Googleでログイン」のような 認証(ユーザーが誰か) を実現するために、OAuthの上に OpenID Connect(OIDC) が定義されています。
OAuth 2.0 + OIDC の場合:
myapp.com が取得できるもの → アクセストークン + IDトークン(JWT)
IDトークンをデコードすると:
{
"sub": "1234567890", ← ユーザーの一意ID(DBの主キーに使える)
"email": "tanaka@gmail.com",
"name": "田中太郎"
}
これが追加でできる:
✅ ユーザーをDBに登録・管理する
✅ 「田中太郎としてログインした」と確認する
✅ 次回ログイン時に同じユーザーを識別する(subが同じ)
OIDCが追加するもの
OIDCがOAuthに追加するのは実質1つだけです:リクエストに scope=openid を加えると、トークンエンドポイントのレスポンスに IDトークン(JWT) が加わります。
// OAuthのみ
{ "access_token": "ya29.xxxxx", "expires_in": 3600 }
// OAuth + OIDC(scope=openid を追加するだけ)
{ "access_token": "ya29.xxxxx", "id_token": "eyJhbGci...", "expires_in": 3600 }
IDトークンをデコードすると:
{
"iss": "https://accounts.google.com", // 発行者(Googleが署名した)
"sub": "1234567890", // ユーザーの一意ID
"email": "tanaka@example.com",
"name": "田中太郎",
"exp": 1600003600 // 有効期限
}
-
sub(Subject)はGoogleが各アプリケーションに対して一意に割り当てる不変のユーザーIDです。これをDBの主キーとしてユーザー管理できます - 署名付きなので、Googleの公開鍵で改ざんを検証できます
OAuthとOIDCの役割分担
| OAuth 2.0 | OpenID Connect | |
|---|---|---|
| 目的 | 認可(権限委譲) | 認証(本人確認) |
| 発行するもの | アクセストークン | IDトークン(JWT) |
| 答える問い | 「このアプリは何をしていいか」 | 「このユーザーは誰か」 |
| 単体でログインに使えるか | ❌ | ✅ |
✅ 「Googleでログイン」 = OAuthで権限委譲 + OIDCでユーザー特定
「Googleでログイン」した後、アプリに何が残るか
OIDCでIDトークンを取得した後、myapp.com のサーバーは次の処理を行います。
[myapp.com サーバー] IDトークンを受け取る
↓
sub(ユーザーの一意ID)でDBを検索
↓
┌─ 初回ログイン → usersテーブルに新規登録(email/nameも保存)
└─ 2回目以降 → 既存ユーザーレコードを照合
↓
アプリ独自のセッションIDを発行
↓
Set-Cookie: session_id=xxxx; HttpOnly; Secure → ブラウザへ返す
この時点でアプリが保持する情報は次のとおりです。
| アプリが保持するもの | 保存先 | 用途 |
|---|---|---|
sub(ユーザー外部ID) |
users テーブル |
ユーザー特定の主キー |
email / name
|
users テーブル |
プロフィールの初期値 |
| セッションID | セッションストア + HTTP-only Cookie | 以降のリクエスト認証 |
| アクセストークン | サーバーサイドのみ | 外部API(Google等)の呼び出し |
重要な点が2つあります:
-
OIDCはセッション確立の入口にすぎない。 IDトークンで誰がログインしたかを確認したら、以降はアプリ独自のセッション管理(セッションID + Cookie)が引き継ぎます。IDトークン自体をセッションCookieとして使い回すのは誤りです(詳細は次の記事)。
-
アクセストークンはブラウザに渡さない。 アクセストークンはGoogle APIを呼ぶためのものであり、サーバーサイドで保持します。ブラウザ(JavaScript)からは見えない場所に置くのが基本です。
✅ 「Googleでログイン」した後のアプリの挙動は、パスワードログイン後と変わらない。
違いは「セッション確立の入口」がOIDCかパスワード検証かの差だけです。
また、MFAが有効なGoogleアカウントでログインした場合、Googleの認証画面上でMFAが処理されます。myapp.comにはMFAの処理結果(認証済みのIDトークン)が届くだけで、myapp.com自身はMFAを実装する必要がありません。MFAはIdP(Google)の責務です。
OAuthが「意味を持つ」ユースケース
OAuthが必須レベルのケース
- 外部サービス連携(Google / GitHub / X など)
- サードパーティアプリ連携
- モバイルアプリ + API
- マイクロサービス間での権限委譲
OAuthが過剰なケース
- 単一Webアプリのみ(外部サービス連携なし)
- 社内限定ツール
- ユーザー数が少ない管理画面
👉 「第三者が介在しない」ならOAuthは不要
応用フロー:Device Authorization Grant(デバイス認証)
ブラウザを持たないデバイスのOAuth
これまで見てきたフローはすべて「ブラウザでリダイレクトできる」という前提でした。しかし次のようなケースではブラウザリダイレクトが使えません。
-
CLIツール(
gh auth login、gcloud auth loginなど) - スマートTV・ゲーム機(入力デバイスが限られている)
- IoTデバイス・プリンター(ブラウザがない)
このようなデバイス向けに定義されているのが Device Authorization Grant(RFC 8628)です。
Device Flowのしくみ
[デバイス(CLIやTV)]
↓ ① Authorization ServerにDevice Codeをリクエスト
[Authorization Server] → device_code と user_code を返す
↓
[デバイス] ユーザーに表示する:
「https://example.com/activate にアクセスして
コード: WXYZ-1234 を入力してください」
↓
[ユーザー] スマートフォンやPCのブラウザで上記URLを開いてコードを入力
↓ ② ユーザーがブラウザで認証・承認
[Authorization Server] ユーザーの承認を記録
↓
[デバイス] ③ Authorization Serverへ定期的にポーリング(数秒おきにトークンを確認)
↓ ユーザーが承認したタイミングで
[Authorization Server] → アクセストークンを返す
↓
[デバイス] アクセストークンを使ってAPIを呼び出す
ポイントは「デバイス」と「ブラウザ」を分離している点です。
デバイス自体はコードを表示するだけで、実際の認証はユーザーが別のデバイス(スマートフォンなど)のブラウザで行います。
具体例:gh auth login
GitHubのCLIツール gh でログインすると、このフローが使われています。
$ gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser
! First copy your one-time code: ABCD-1234
Press Enter to open github.com in your browser...
ターミナルに表示される ABCD-1234 が user_code です。
ブラウザでGitHubにアクセスしてこのコードを入力することで、CLIがアクセストークンを取得します。
Webアプリフローとの違い
| Webアプリ(Authorization Code Flow) | デバイス(Device Flow) | |
|---|---|---|
| リダイレクト | ブラウザがリダイレクトする | なし |
| 認証場所 | 同じデバイスのブラウザ | 別のデバイスのブラウザ |
| トークン取得 | コールバックで受け取る | ポーリングで取得する |
| 適したクライアント | Webアプリ・モバイル | CLI・TV・IoT |
まとめ
| 方式 | 本質 | 限界 |
|---|---|---|
| Basic認証 | 秘密情報をリクエストに毎回添付 | 第三者委譲不可 |
| セッション認証 | 秘密情報をCookieとサーバーに格納 | ブラウザ前提・API不向き |
| APIキー認証 | 秘密情報を環境変数等に格納 | ユーザー単位の委譲不可 |
| SAML | XMLアサーションで認証情報を連携 | 実装が重い・モダンAPI不向き |
| OAuth 2.0 | パスワード不要で権限だけを委譲 | 認証(本人確認)は別途必要 |
| OIDC | OAuthの上でユーザー認証を実現 | OAuth前提 |
- OAuthは 「認証」ではなく「認可の委譲」 の仕組み
- 「Googleでログイン」は OAuth(権限委譲) + OIDC(本人確認) の組み合わせ
- ブラウザが開くのは、ユーザーが正規の認証サーバーに直接ログインするため
- OIDCでIDトークンを取得した後は、アプリ独自のセッションを発行して管理する(IDトークンをセッションに使い回さない)
- ブラウザを持たないデバイスには Device Flow(user_code + ポーリング) を使う
- 単純なWebアプリに無理に使うものではない
さらに学びたい人向け:公式ドキュメント
OAuth 2.0
-
RFC 6749 - The OAuth 2.0 Authorization Framework
OAuth 2.0の仕様原文。4つのグラントタイプ(Authorization Code、Implicit、Client Credentials、Resource Owner Password Credentials)の詳細が定義されています。 -
RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
アクセストークン(Bearerトークン)をHTTPリクエストに載せる方法の仕様。
PKCE
-
RFC 7636 - Proof Key for Code Exchange by OAuth 2.0 Public Clients
PKCEの仕様原文。モバイルアプリ・SPAなどパブリッククライアントにおける認可コード横取り攻撃の対策として標準化されています。
ネイティブアプリのOAuth
-
RFC 8252 - OAuth 2.0 for Native Apps
デスクトップ・モバイルアプリでOAuthを使う際のベストプラクティス。カスタムURIスキームや外部ブラウザの利用方針が定義されています。
デバイス認証
-
RFC 8628 - OAuth 2.0 Device Authorization Grant
CLIツール・TV・IoTデバイスなどブラウザリダイレクトが使えない環境向けのOAuthフロー。device_code/user_codeのやり取りとポーリングによるトークン取得が定義されています。
OpenID Connect
-
OpenID Connect Core 1.0
OIDCの仕様原文。IDトークンの構造、sub/iss/audなどのクレーム、UserInfoエンドポイントの仕様が定義されています。 -
OpenID Connect Discovery 1.0
OIDCプロバイダが自身のエンドポイントやサポートする機能を公開するための仕様(/.well-known/openid-configuration)。
SAML 2.0
-
OASIS SAML 2.0 Technical Overview
SAML 2.0の概要ドキュメント。アサーションの構造やSSOのフローが説明されています。
Discussion