Open1

【Auth/認証系】Bearer トークン認証について📝

まさぴょん🐱まさぴょん🐱

Auth/認証系】Bearer トークン認証について📝

Bearer トークン認証とは?

観点 説明
概要 HTTP ヘッダー Authorization: Bearer <token>トークン(資格情報)を渡してアクセスを許可する仕組み。OAuth 2.0 仕様で導入され、Web API で最も一般的なトークンベース認証方式です。
トークンの形 - JWT (JSON Web Token) : 署名付きの自己完結型。
- Opaque token : ランダム文字列。署名はなく、サーバー側ストアや introspection で検証。
代表的な流れ ➊ ユーザーがログイン → ➋ サーバーがトークン発行 → ➌ クライアントはヘッダーに付与して API 呼び出し → ➍ サーバーはトークンを検証し許可 / 拒否。
利点 セッションレス(特に JWT)、Cookie 依存なし、モバイル/SPA でも簡単に利用可。
注意点 HTTPS が必須 - プレーン HTTP では盗聴される
⭐ トークン失効(期限、Black-list)と更新(Refresh Token)戦略
⭐ クロスサイト XSS → ブラウザ保管場所は httpOnly Cookie かメモリ推奨

TypeScript 実装サンプル

以下は Node.js (Express)Hono (Cloudflare/Bun/Node 実装可) の 2 パターンです。どちらも JWT を Bearer トークンとして採用し、環境変数 JWT_SECRET で署名鍵を管理します。

1. Express + jsonwebtoken

// src/server.ts
import express, { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
dotenv.config();

const app = express();
app.use(express.json());

const SECRET = process.env.JWT_SECRET ?? 'development-secret';

// 型拡張して user 情報をミドルウェア以降に渡す
interface AuthRequest extends Request {
  user?: string | jwt.JwtPayload;
}

// ───────────────────────────────────
// ミドルウェア: Bearer トークン検証
// ───────────────────────────────────
function authenticate(req: AuthRequest, res: Response, next: NextFunction) {
  const header = req.headers.authorization ?? '';
  if (!header.startsWith('Bearer ')) {
    return res.status(401).json({ message: 'Missing Bearer token' });
  }

  const token = header.slice(7); // "Bearer ".length === 7
  try {
    const payload = jwt.verify(token, SECRET);
    req.user = payload;          // 後続に渡す
    next();
  } catch {
    return res.status(401).json({ message: 'Invalid / expired token' });
  }
}

// ───────────────────────────────────
// ログイン: 正常なら 1時間 有効な JWT を発行
// ───────────────────────────────────
app.post('/login', (req, res) => {
  const { username, password } = req.body as { username: string; password: string };

  // ★ 実際は DB などで認証
  if (username === 'alice' && password === 'wonderland') {
    const token = jwt.sign({ sub: username }, SECRET, { expiresIn: '1h' });
    return res.json({ token });
  }
  res.status(401).json({ message: 'Bad credentials' });
});

// ───────────────────────────────────
// 公開エンドポイント
// ───────────────────────────────────
app.get('/public', (_req, res) => res.json({ message: 'Hello, world!' }));

// ───────────────────────────────────
// 保護されたエンドポイント
// ───────────────────────────────────
app.get('/protected', authenticate, (req: AuthRequest, res) => {
  res.json({ message: `Hello ${req.user && (req.user as any).sub}!` });
});

app.listen(3000, () => console.log('Listening on :3000'));
# ログインしてトークン取得
curl -X POST localhost:3000/login -d '{"username":"alice","password":"wonderland"}' \
     -H "Content-Type: application/json"

# Bearer ヘッダーを付けてアクセス
curl localhost:3000/protected -H "Authorization: Bearer <token>"

2. Hono + hono/jwt (Edge でも動く軽量フレームワーク)

// src/index.ts
import { Hono } from 'hono';
import { sign, verify } from 'hono/jwt';

const app = new Hono<{ Variables: { user: string } }>();
const SECRET = process.env.JWT_SECRET ?? 'development-secret';

/* ───── 認証ヘルパ ───── */
async function auth(c: Hono.Context, next: Hono.Next) {
  const header = c.req.header('Authorization') ?? '';
  if (!header.startsWith('Bearer ')) return c.text('Unauthorized', 401);

  try {
    const token = header.slice(7);
    const payload = await verify(token, SECRET);
    c.set('user', payload.sub as string);
    await next();
  } catch {
    return c.text('Invalid / expired token', 401);
  }
}

/* ───── ルーティング ───── */
app.post('/login', async (c) => {
  const { username, password } = await c.req.json();
  if (username === 'alice' && password === 'wonderland') {
    const token = await sign({ sub: username }, SECRET, { exp: Math.floor(Date.now()/1000) + 3600 });
    return c.json({ token });
  }
  return c.json({ message: 'Bad credentials' }, 401);
});

app.get('/public', (c) => c.json({ message: '公開エンドポイント' }));
app.get('/protected/hello', auth, (c) => c.json({ message: `Hello ${c.get('user')}` }));

export default app;

実行方法一例(Bun)

bun run src/index.ts

よくある質問 (FAQ)

質問 回答
トークンはどこに保存すべき? ブラウザなら httpOnly + Secure Cookie が最も安全。LocalStorage は XSS に弱いので避ける。モバイルアプリなら OS の安全なキーチェーン。
期限切れ時の自動更新は? AccessToken は短命(15 m〜1 h など)・RefreshToken は長命で保護度を高め、/auth/refresh エンドポイントで再発行。
複数サービスで共有したい JWT であれば各サービスが同じ署名鍵か JWKS にアクセスできれば中央ストアは不要。逆に無効化が難しいので注意。

まとめ

Bearer トークン認証は シンプル かつ ステートレス に API を保護できます。

  • JWT なら検証だけで DB アクセス不要、
  • Opaque ならトークン盗難時の即時失効が容易、

というトレードオフを把握し、HTTPS・XSS 対策・トークン更新 の 3 本柱を押さえれば、安全に運用できます。