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 本柱を押さえれば、安全に運用できます。