🔑

【OAuth 2.0】「なんとなく実装」は危険!認可コードフローの正しい実装とセキュリティ対策

に公開

「OAuth 2.0でログイン機能を実装したいけど、フローが複雑でなんとなくコピペで済ませていないでしょうか?」 😰

実は、OAuthの実装方法を誤ると、アクセストークンが流出したり、アカウント乗っ取りの被害に遭うリスクがあります。特に、古くからある解説記事の中には、現在では非推奨とされている手法を紹介しているものも少なくありません。

この記事では、現在最もセキュアで推奨されている「認可コードフロー(Authorization Code Flow)」の仕組みと、絶対にやってはいけない実装パターンについて解説します。

OAuth 2.0とは?

OAuth 2.0は、認可(Authorization) のためのフレームワークです。「ユーザー(リソースオーナー)が、あるアプリ(クライアント)に対して、別のサービスにある自分のデータ(リソース)へのアクセス権限を与える」ための仕組みです。

ID/パスワードをアプリに預けることなく、安全に連携できるのが最大の特徴です。

登場人物とフロー

OAuth 2.0には主に4つの役割が登場します。

OAuth 2.0の抽象的なフロー

  1. リソースオーナー: データの持ち主(あなた)
  2. クライアント: 連携したいアプリ
  3. 認可サーバー: 権限付与を管理するサーバー(Google, Twitterなど)
  4. リソースサーバー: データを持っているサーバー(API)

まとめ:その実装、大丈夫?

フローの選択を間違えると脆弱性につながります。

OAuth 2.0のフロー比較

フロー名 特徴 現在の推奨度
認可コードフロー バックエンドでトークン交換を行う。トークンがブラウザに露出しない。 ⭐️ 推奨 (Standard)
インプリシットフロー URLフラグメントでトークンを受け取る。履歴に残る危険性あり。 ❌ 非推奨 (Deprecated)
クライアントクレデンシャル ユーザー不在。サーバー間通信で使用。 用途限定

具体的な実装のポイント

セキュリティを高めるために、以下の対策が必須です。

1. state パラメータでCSRF対策

認可リクエストを送る際、ランダムな文字列(state)を生成してセッションに保存し、パラメータに含めます。コールバックが戻ってきたときに、送った state と一致するか確認します。これがないと、攻撃者が用意したアカウントに強制的に連携させられる可能性があります。

// 1. 認可リクエスト時
const state = generateRandomString();
session.state = state;
res.redirect(`https://auth-server.com/authorize?response_type=code&client_id=MY_ID&state=${state}&...`);

// 2. コールバック受信時
if (req.query.state !== session.state) {
  throw new Error("State mismatch! CSRF attack detected.");
}

2. Client Secret は絶対に漏らさない

「認可コード」を「アクセストークン」に交換する際、client_secret を使用します。この処理は必ずバックエンドサーバーで行ってください。

フロントエンド(SPAやモバイルアプリ)に client_secret を埋め込むと、リバースエンジニアリングで簡単に抜き取られてしまいます。

3. リフレッシュトークンの取り扱い

アクセストークンの有効期限は短く(例:1時間)、リフレッシュトークンを使って更新します。リフレッシュトークンが漏れると永続的にアクセスされてしまうため、より厳重な管理が必要です(HttpOnly Cookieへの保存など)。

実装例:Node.js Expressでの認可コード交換

// コールバック処理のイメージ
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // 1. Stateの検証
  if (state !== req.session.state) return res.status(403).send('Invalid state');

  // 2. 認可コードをアクセストークンと交換(バックエンド通信)
  const tokenResponse = await axios.post('https://auth-server.com/token', {
    grant_type: 'authorization_code',
    code,
    redirect_uri: REDIRECT_URI,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET // サーバー側でのみ保持
  });

  const { access_token } = tokenResponse.data;

  // 3. アクセストークンを使ってAPIアクセス
  // ...
});

おわりに

OAuth 2.0は強力ですが、正しく実装しないとセキュリティホールになります。「動けばいい」ではなく、推奨されているフロー(特に認可コードフロー + PKCE)を選択し、各パラメータの意味を理解して実装しましょう。

セキュリティ事故を防ぐためにも、ライブラリ(NextAuth.jsなど)を活用するのも一つの手ですが、裏側の仕組みを知っておくことは重要です。

Discussion