Webセキュリティ

OAuth 2.0の仕組みを図解!認可フローを完全理解

セキュリティ

OAuth 2.0の仕組みを図解!
認可フローを完全理解

OAuth 2.0は「Googleでログイン」等の外部認証の仕組みです。認可コードフローを中心に解説します。

こんな人向けの記事です

  • OAuth 2.0の仕組みを理解したい人
  • ソーシャルログインを実装したい人
  • 認証と認可の違いを知りたい人

Step 1OAuthとは何か

OAuth 2.0は認可(Authorization)のためのプロトコルです。パスワードを渡さずに、第三者アプリにリソースへのアクセスを許可できます。

認証と認可の違い
# 認証(Authentication)= 「あなたは誰?」
# → ユーザーIDとパスワードで本人確認
# → 例: ログイン

# 認可(Authorization)= 「何を許可する?」
# → 第三者にリソースへのアクセス権を付与
# → 例: 「このアプリにGoogleカレンダーの読み取りを許可しますか?」

# OAuth 2.0 は「認可」のプロトコル
# OpenID Connect (OIDC) が OAuth 2.0 の上に認証を追加したもの
身近な例
「Googleでログイン」「GitHubでログイン」ボタンは、OAuth 2.0 + OpenID Connect を使っています。

Step 24つの登場人物

OAuth 2.0の登場人物
# 1. リソースオーナー(Resource Owner)
#    → ユーザー自身。データの持ち主
#    → 例: Googleアカウントを持つ田中さん

# 2. クライアント(Client)
#    → ユーザーのデータにアクセスしたいアプリ
#    → 例: カレンダー管理アプリ

# 3. 認可サーバー(Authorization Server)
#    → アクセス許可を管理するサーバー
#    → 例: Googleの認可サーバー

# 4. リソースサーバー(Resource Server)
#    → ユーザーのデータを保持するサーバー
#    → 例: Google Calendar API

# フロー概要:
# ユーザー → 「Googleでログイン」クリック
# → Google認可画面 → 「許可する」
# → アプリがアクセストークンを取得
# → トークンでGoogle APIにアクセス

Step 3認可コードフロー

最も安全で推奨されるフローです。サーバーサイドアプリケーション向けです。

認可コードフロー(Authorization Code Flow)
# Step 1: ユーザーを認可エンドポイントにリダイレクト
# GET https://accounts.google.com/o/oauth2/v2/auth?
#   response_type=code
#   &client_id=YOUR_CLIENT_ID
#   &redirect_uri=https://yourapp.com/callback
#   &scope=openid email profile
#   &state=ランダムな文字列(CSRF対策)

# Step 2: ユーザーが「許可」→ コールバックURLに認可コードが届く
# GET https://yourapp.com/callback?code=認可コード&state=ランダムな文字列

# Step 3: 認可コードをアクセストークンに交換(サーバー間通信)
# POST https://oauth2.googleapis.com/token
# {
#   "code": "認可コード",
#   "client_id": "YOUR_CLIENT_ID",
#   "client_secret": "YOUR_CLIENT_SECRET",
#   "redirect_uri": "https://yourapp.com/callback",
#   "grant_type": "authorization_code"
# }

# Step 4: アクセストークンでAPIにアクセス
# GET https://www.googleapis.com/calendar/v3/calendars
# Authorization: Bearer アクセストークン
client_secretの管理
client_secretは絶対にフロントエンド(JavaScript)に含めないでください。サーバーサイドでのみ使用します。

Step 4アクセストークンとリフレッシュトークン

トークンの仕組み
# アクセストークン
# - APIアクセスに使用する短命のトークン(通常1時間)
# - 期限切れになったら再取得が必要

# リフレッシュトークン
# - 新しいアクセストークンを取得するための長命のトークン
# - ユーザーの再ログインなしでトークンを更新

# トークン更新のフロー
# POST https://oauth2.googleapis.com/token
# {
#   "refresh_token": "リフレッシュトークン",
#   "client_id": "YOUR_CLIENT_ID",
#   "client_secret": "YOUR_CLIENT_SECRET",
#   "grant_type": "refresh_token"
# }

# レスポンス:
# {
#   "access_token": "新しいアクセストークン",
#   "expires_in": 3600,
#   "token_type": "Bearer"
# }

Step 5実装例(Python)

Python(Flask + requests-oauthlib)
# pip install Flask requests-oauthlib
from flask import Flask, redirect, request, session
from requests_oauthlib import OAuth2Session

app = Flask(__name__)
app.secret_key = "your-secret-key"

CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
AUTHORIZATION_URL = "https://accounts.google.com/o/oauth2/v2/auth"
TOKEN_URL = "https://oauth2.googleapis.com/token"
REDIRECT_URI = "http://localhost:5000/callback"
SCOPE = ["openid", "email", "profile"]

@app.route("/login")
def login():
    oauth = OAuth2Session(CLIENT_ID, redirect_uri=REDIRECT_URI, scope=SCOPE)
    auth_url, state = oauth.authorization_url(AUTHORIZATION_URL)
    session["oauth_state"] = state
    return redirect(auth_url)

@app.route("/callback")
def callback():
    oauth = OAuth2Session(CLIENT_ID, state=session["oauth_state"],
                          redirect_uri=REDIRECT_URI)
    token = oauth.fetch_token(TOKEN_URL, client_secret=CLIENT_SECRET,
                              authorization_response=request.url)
    session["oauth_token"] = token

    # ユーザー情報を取得
    resp = oauth.get("https://www.googleapis.com/oauth2/v3/userinfo")
    user_info = resp.json()
    return f"ようこそ {user_info['name']} さん!"

Step 6セキュリティの注意点

セキュリティ対策
# 1. state パラメータ(CSRF対策)
#    認可リクエストにランダムな state を含め、
#    コールバックで一致を検証する

# 2. PKCE(Proof Key for Code Exchange)
#    SPAやモバイルアプリで必須。
#    code_verifier と code_challenge でトークン交換を保護

# 3. redirect_uri の厳密な検証
#    登録済みのURIと完全一致のみ許可(オープンリダイレクト防止)

# 4. トークンの安全な保管
#    - サーバーサイド: 暗号化してDB保存
#    - フロントエンド: httpOnly Cookie(localStorageは避ける)

# 5. スコープの最小化
#    必要最小限の権限のみ要求する
#    NG: scope=全権限
#    OK: scope=email profile(必要なものだけ)
PKCE とは
SPA(シングルページアプリ)ではclient_secretを安全に保持できないため、PKCEを使って認可コードの横取りを防ぎます。現在はサーバーサイドアプリでもPKCEの使用が推奨されています。

まとめ

  • OAuth 2.0は認可(アクセス権の委譲)のプロトコル
  • 認可コードフローが最も安全で推奨
  • アクセストークン(短命)+ リフレッシュトークン(長命)
  • state パラメータでCSRF対策、PKCEでコード横取り防止
  • client_secretはサーバーサイドでのみ使用