Webアプリケーションの認証機構:Cookieとトークンの比較と実装

認証の基礎概念:PINについて

認証システムにおいて、PIN(Personal Identification Number)はユーザーの身元を確認するための短い数字のパスコードです。

役割と特徴

  • 従来のパスワードの代わり、または補助として利用され、迅速な本人確認(スマートフォンのロック解除や決済アプリのアクセスなど)に用いられます。
  • 文字列に比べて桁数が少ないため、単体でのセキュリティ強度は低く、生体認証やSMS認証との組み合わせ(多要素認証)が推奨されます。
  • デバイスのロック解除や高頻度・低リスクな場面で重宝されます。

利用シーン

  • デバイス認証:スマートフォンやPCの起動時、スマートロックの解除
  • 金融取引:キャッシュカードの暗証番号、モバイル決済アプリの認証
  • エンタープライズ:パスワードとPINを組み合わせた2段階認証
  • 一時的なアクセス:会議室のWi-Fiパスコードや共有端末の限定的ログイン

PINとパスワードの比較

比較項目 PIN パスワード
文字構成 4〜6桁程度の数字のみ 8文字以上の文字・記号混在
セキュリティの前提 デバイス内の安全な領域に保存* サーバー側でハッシュ化して保存
主な用途 ローカルデバイスの高速アンロック アカウント全体の強力な認証

*注:PINはデバイス内に閉じているため、端末自体が侵害されない限り安全です。一方、パスワードはネットワーク越しに検証されるため、サーバー側の漏洩リスクがあります。

セキュリティのベストプラクティス

  • 使い回しの回避:異なるサービスやデバイスで同一のPINを設定しない。
  • 多要素認証の活用:PIN+指紋認証、PIN+ワンタイムパスワードなどの組み合わせを推奨。
  • 定期的な更新:機微なサービス(オンラインバンキングなど)では定期的に変更する。

Cookieを用いた認証メカニズム

HTTPプロトコルはステートレスであるため、リクエストごとにCookieを付与してユーザーのセッションを維持します。サーバーはクライアントに対してSet-CookieヘッダーでCookieを発行でき、その容量は約4KBに制限されています。また、デフォルトでクロスドメイン制約があり、異なるドメイン間でのCookie共有は行われません。

サードパーティJSによるトラッキングとSameSite属性

近年のブラウザは、サードパーティのJavaScriptによるCookie設定を厳しく制限するようになっています。これはユーザートラッキングを防ぐためです。

例えば、スマートフォンのレビューサイトを運営しており、収益化のために外部のECサイト(例:shop.example.com)の広告バナーを掲載するとします。広告のタグが以下のようになっていたとします。

<img src="https://shop.example.com/track?item=smartphone_x" />

広告のJavaScriptはレビューサイトのCookieにはアクセスできませんが、ページのコンテンツ(スマートフォン関連の情報)を把握できます。もしユーザーが過去にそのECサイトへログインしていれば、上記リクエストにはECサイトのCookieが自動的に付与され、ECサイトは「誰が」「どの商品に興味を持っているか」を把握できます。これが広告トラッキングの仕組みです。

このようなCSRF攻撃やトラッキングを防ぐため、CookieにはSameSite属性が導入されました。SameSite=Strictを設定すると、外部のサイトから自サイトへのリクエストにおいてCookieが全く送信されなくなります。つまり、現在のページURLとリクエスト先が完全に一致する場合のみCookieが送信されます。

Cookieとセッションの連携

  • Cookie(クライアント側):ユーザー識別子(例:sessionId)を保存します。
  • セッション(サーバー側):ユーザーの詳細な状態データを保持し、Cookieの識別子と紐付けます。

セッションを利用する主な理由は以下の通りです:

  1. セキュリティの向上:機密情報をクライアント側に保持しないため、改ざんリスクを減らせます。
  2. 容量の制約回避:Cookieの4KB制限を超えるデータをサーバー側で保持できます。
  3. 通信の効率化:リクエストのたびに送信されるデータ量が少なくなるため、通信が高速化されます。

Cookieとトークンの比較

  • 送信方式:CookieはHTTPの仕様により自動的にヘッダーに付与されますが、トークンは開発者が明示的に(例:Authorizationヘッダーとして)送信する必要があります。
  • 保存先:Cookieはブラウザが自動的に保存・管理しますが、トークンはlocalStoragesessionStorageなどで手動で管理する必要があります。
  • ドメイン制約:Cookieにはクロスドメイン制限がありますが、トークンにはありません。

Cookieは標準化された認証手法ですが、モバイルアプリやAPI駆動のモダンなアーキテクチャにおいて柔軟性に欠けるため、制約の少ないトークンベースの認証が広く採用されるようになっています。

JWT (JSON Web Token) によるトークン実装

  1. クライアントがログイン情報を送信し、サーバーが認証に成功すると、暗号化されたトークンを返却します。
  2. クライアントはこのトークンを自身で保存します(トークン内にはユーザー情報が非対称暗号などにより安全にエンコードされています)。
  3. 以降のAPIリクエストでは、リクエストヘッダーにトークンを含めて送信し、サーバー側で検証を行います。

実装例:TaroとEgg.jsを用いたWeChatミニプログラムのログイン

Taroを使用したWeChatミニプログラムでは、まずユーザーが認証を許可した後、Taro.getUserProfile()などのAPIを呼び出してユーザーのプロフィール情報を取得します。

バックエンドのEgg.jsにおけるCSRF対策ミドルウェアは、デフォルトでPOSTPUTDELETEPATCHなどの状態変更リクエストに対してのみCSRFトークンの検証を行います。GETHEADOPTIONSなどの読み取り専用リクエストには検証を行わない仕様になっており、不要なオーバーヘッドを防いでいます。

JWTとRedisの導入

認証システムを構築するために、必要なパッケージをインストールします。

npm install egg-jwt egg-redis

JWTの署名と検証ロジック

コントローラー内でトークンを生成し、レスポンスとして返却するロジックの例です。

// Controller: ユーザー認証とトークン生成
async login() {
  const { ctx, app } = this;
  const { username, password } = ctx.request.body;

  // ユーザー検証ロジック(省略)
  const targetUser = await ctx.service.user.verifyCredentials(username, password);

  if (targetUser) {
    // 秘密鍵を使用してJWTを生成
    const accessToken = app.jwt.sign(
      { uid: targetUser.id, role: targetUser.role },
      app.config.jwt.secret,
      { expiresIn: '2h' }
    );
    ctx.body = { success: true, token: accessToken };
  } else {
    ctx.throw(401, '認証に失敗しました');
  }
}

タグ: JWT cookie Session SameSite Taro

5月26日 18:54 投稿