LESSON 30分

OAuth 2.0フローを理解しよう

ストーリー

「JWTは自前の認証システムだ。でも実際のサービスでは、 GoogleやGitHubアカウントでログインする機能をよく見るだろう?」

高橋さんが問いかける。

「ソーシャルログインですね。あれはどういう仕組みなんですか?」

「あれがOAuth 2.0だ。ユーザーのパスワードを預からずに、 外部サービスの認証を利用できる仕組みだ。 パスワードを管理するリスクを減らせるメリットがある」


OAuth 2.0とは

OAuth 2.0は、サードパーティのアプリケーションがユーザーのリソースに安全にアクセスするための認可フレームワークです。

従来のやり方の問題

パスワードを直接共有する方式(危険):

ユーザー: 「サービスAにGoogleの連絡先を使わせたい」
サービスA: 「Googleのパスワードを教えてください」
ユーザー: 「パスワードは abc123 です」

問題:
- サービスAにGoogleの全権限を渡してしまう
- サービスAがパスワードを保存するリスク
- パスワード変更時に全サービスに再通知が必要

OAuth 2.0の解決策

OAuth 2.0 方式(安全):

ユーザー: 「サービスAにGoogleの連絡先を使わせたい」
サービスA: 「Googleにリダイレクトします」
Google: 「サービスAに連絡先へのアクセスを許可しますか?」
ユーザー: 「許可します」
Google: 「サービスA、これがアクセストークンです(連絡先のみ)」

メリット:
- パスワードを共有しない
- 必要な権限のみを許可
- いつでも権限を取り消せる

OAuth 2.0の登場人物

役割説明
Resource Ownerリソースの所有者ユーザー
Clientリソースへのアクセスを求めるアプリあなたのWebアプリ
Authorization Server認可を管理するサーバーGoogle、GitHub
Resource Serverリソースを提供するサーバーGoogle API、GitHub API

認可コードフロー(Authorization Code Flow)

最も安全で推奨されるフローです。

ユーザー        クライアント(あなたのアプリ)     認可サーバー(Google)
  │                    │                           │
  │  1. ログインボタンクリック                        │
  │──────────────→     │                           │
  │                    │  2. 認可リクエスト           │
  │                    │─────────────────────────→  │
  │                    │                           │
  │  3. ログイン画面を表示                           │
  │←─────────────────────────────────────────────  │
  │                    │                           │
  │  4. ログイン + 権限の許可                        │
  │──────────────────────────────────────────────→ │
  │                    │                           │
  │                    │  5. 認可コードを返す         │
  │  (リダイレクト)    │←─────────────────────────  │
  │←──────────────     │                           │
  │                    │                           │
  │                    │  6. 認可コード → トークン交換 │
  │                    │─────────────────────────→  │
  │                    │                           │
  │                    │  7. アクセストークン発行      │
  │                    │←─────────────────────────  │
  │                    │                           │
  │  8. ログイン完了    │                           │
  │←──────────────     │                           │

各ステップの詳細

Step 2: 認可リクエスト

GET https://accounts.google.com/o/oauth2/v2/auth?
  response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://your-app.com/callback
  &scope=openid email profile
  &state=random_csrf_token
パラメータ説明
response_typecode を指定(認可コードフロー)
client_idアプリのID(事前登録で取得)
redirect_uri認可後のリダイレクト先
scope要求する権限
stateCSRF対策のランダム値

Step 5: 認可コードの受け取り

GET https://your-app.com/callback?
  code=AUTHORIZATION_CODE
  &state=random_csrf_token

Step 6: トークン交換

typescript
// サーバーサイドで認可コードをトークンに交換
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    code: authorizationCode,
    client_id: process.env.GOOGLE_CLIENT_ID!,
    client_secret: process.env.GOOGLE_CLIENT_SECRET!, // サーバーサイドのみ
    redirect_uri: 'https://your-app.com/callback',
    grant_type: 'authorization_code'
  })
});

const { access_token, refresh_token, id_token } = await tokenResponse.json();

OpenID Connect(OIDC)

OAuth 2.0は「認可」のための仕組みですが、OpenID Connectは OAuth 2.0の上に「認証」のレイヤーを追加した規格です。

OAuth 2.0:
  「サービスAにGoogleの連絡先へのアクセスを許可する」(認可)

OpenID Connect:
  「このユーザーは Google アカウント tanaka@gmail.com である」(認証)
  + OAuth 2.0 の認可機能

ID Token

OpenID Connect では、アクセストークンに加えてIDトークン(JWT形式)が発行されます。

json
{
  "iss": "https://accounts.google.com",
  "sub": "1234567890",
  "email": "tanaka@gmail.com",
  "name": "田中太郎",
  "picture": "https://...",
  "iat": 1640000000,
  "exp": 1640003600
}

ソーシャルログインの実装概要

Express での実装例(Passport.js)

typescript
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    callbackURL: '/auth/google/callback'
  },
  async (accessToken, refreshToken, profile, done) => {
    // ユーザーをDBで検索または新規作成
    let user = await db.findUserByGoogleId(profile.id);

    if (!user) {
      user = await db.createUser({
        googleId: profile.id,
        name: profile.displayName,
        email: profile.emails?.[0]?.value
      });
    }

    done(null, user);
  }
));

// ログイン開始
app.get('/auth/google', passport.authenticate('google', {
  scope: ['openid', 'email', 'profile']
}));

// コールバック
app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    // ログイン成功 → JWTを発行
    const token = generateAccessToken(req.user);
    res.redirect(`/dashboard?token=${token}`);
  }
);

OAuth 2.0のセキュリティ上の注意点

注意点対策
CSRF攻撃stateパラメータでランダム値を検証
認可コードの横取りPKCE(Proof Key for Code Exchange)を使用
リダイレクトURIの改ざん事前登録したURIのみ許可
client_secretの漏洩サーバーサイドでのみ使用、環境変数で管理
トークンの過剰な権限必要最小限のscopeのみ要求

まとめ

ポイント内容
OAuth 2.0パスワードを共有せずに権限を委譲する仕組み
認可コードフロー最も安全な推奨フロー
OpenID ConnectOAuth 2.0 + 認証(IDトークン)
ソーシャルログインGoogle/GitHub等のアカウントでログイン

チェックリスト

  • OAuth 2.0の4つの登場人物を説明できる
  • 認可コードフローの流れを理解した
  • OAuth 2.0とOpenID Connectの違いを理解した
  • stateパラメータの目的を理解した

次のステップへ

OAuth 2.0の仕組みを理解しました。 次のセクションでは、これまでの知識を活用して認証システムを実装する演習に取り組みます。


推定読了時間: 30分