Bearer 認証
Bearer 認証は、HTTP Authorization ヘッダーを使用してトークンベースの認証を行う仕組みです。
Bearer 認証とは
Bearer 認証は、RFC 6750 で定義されている OAuth 2.0 のアクセストークンを HTTP リクエストで送信する標準的な方法です。
基本的な仕組み
- クライアントが認証サーバーからアクセストークンを取得
- 以降のAPIリクエストで、そのトークンを Authorization ヘッダーに含めて送信
- サーバーがトークンを検証してリクエストを処理
GET /api/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
"Bearer" という名前の由来
"Bearer" は「所持者」「保持者」を意味します。このトークンを持っている(=Bearer)者が、そのトークンに紐づく権限を行使できるという考え方に基づいています。
トークンの形式
JWT (JSON Web Token)
Bearer トークンとして最もよく使われるのが JWT です。
JWT の構造:
ヘッダー.ペイロード.署名
実例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
デコード後:
ヘッダー:
{
"alg": "HS256",
"typ": "JWT"
}
ペイロード:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
Opaque トークン
JWT とは異なり、トークン自体には情報が含まれず、サーバー側でのみ意味を持つトークンです。
d8f7a3b2c1e9f4d6a5b8c7e2f1a9d3b4
- トークンIDやセッションIDとしてデータベースで管理
- トークンの内容を知るには、認可サーバーに問い合わせが必要
実装方法
クライアント側の実装
JavaScript (Fetch API)
async function fetchUserProfile(accessToken) {
const response = await fetch('https://api.example.com/users/me', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to fetch user profile');
}
return await response.json();
}
Axios を使用した例
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
});
// インターセプターでトークンを自動付与
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 401エラー時の自動リフレッシュ
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await axios.post(
'https://auth.example.com/token',
{
grant_type: 'refresh_token',
refresh_token: refreshToken,
}
);
const { access_token } = response.data;
localStorage.setItem('accessToken', access_token);
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return api(originalRequest);
} catch (refreshError) {
// リフレッシュ失敗時はログアウト
localStorage.clear();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
サーバー側の実装
Node.js + Express + JWT
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const SECRET_KEY = process.env.JWT_SECRET;
// 認証ミドルウェア
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = user;
next();
});
}
// 保護されたエンドポイント
app.get('/api/users/me', authenticateToken, (req, res) => {
res.json({
id: req.user.sub,
name: req.user.name,
email: req.user.email,
});
});
// トークン発行エンドポイント
app.post('/auth/login', async (req, res) => {
const { username, password } = req.body;
// ユーザー認証(実際にはデータベースで検証)
const user = await authenticateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// アクセストークン生成
const accessToken = jwt.sign(
{
sub: user.id,
name: user.name,
email: user.email,
},
SECRET_KEY,
{ expiresIn: '15m' } // 15分
);
// リフレッシュトークン生成
const refreshToken = jwt.sign(
{ sub: user.id },
SECRET_KEY,
{ expiresIn: '7d' } // 7日間
);
res.json({
access_token: accessToken,
refresh_token: refreshToken,
token_type: 'Bearer',
expires_in: 900, // 秒
});
});
ASP.NET Core
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// JWT認証の設定
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
)
};
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// 保護されたエンドポイント
app.MapGet("/api/users/me", (HttpContext context) =>
{
var userId = context.User.FindFirst("sub")?.Value;
var userName = context.User.FindFirst("name")?.Value;
return Results.Ok(new { id = userId, name = userName });
})
.RequireAuthorization();
app.Run();
セキュリティ上の考慮事項
トークンの保管場所
❌ LocalStorage に保存(非推奨)
// XSS攻撃に脆弱
localStorage.setItem('accessToken', token);
問題点:
- JavaScript からアクセス可能なため、XSS 攻撃で盗まれる可能性が高い
- 悪意のあるスクリプトがトークンを読み取り、外部に送信できる
✅ メモリ内に保持(推奨)
// クロージャやReact Contextで管理
let accessToken = null;
function setToken(token) {
accessToken = token;
}
function getToken() {
return accessToken;
}
メリット:
- XSS 攻撃のリスクを軽減
- ページリロード時にはトークンが失われる(セキュリティ向上)
デメリット:
- ページリロードのたびに再ログインが必要
- リフレッシュトークンの管理が必要
HTTPS の必須化
Bearer トークンは平文で送信されるため、必ず HTTPS を使用する必要があります。
// 開発環境以外ではHTTPSを強制
if (process.env.NODE_ENV === 'production' && req.protocol !== 'https') {
return res.redirect(`https://${req.hostname}${req.url}`);
}
トークンの有効期限
- アクセストークン: 短時間(5〜30分)
- リフレッシュトークン: 長時間(数日〜数週間)
// アクセストークン: 15分
const accessToken = jwt.sign(payload, SECRET, { expiresIn: '15m' });
// リフレッシュトークン: 7日
const refreshToken = jwt.sign({ sub: userId }, SECRET, { expiresIn: '7d' });
CORS の設定
app.use(cors({
origin: 'https://yourapp.com',
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
}));
トークンのブラックリスト化
ログアウト時やトークン漏洩時に、トークンを無効化する仕組みを実装します。
const blacklistedTokens = new Set();
function invalidateToken(token) {
blacklistedTokens.add(token);
// Redis など外部ストレージに保存することを推奨
}
function authenticateToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (blacklistedTokens.has(token)) {
return res.status(401).json({ error: 'Token has been revoked' });
}
// 通常のトークン検証処理
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
}
Bearer 認証の利点と欠点
利点
-
ステートレス
- サーバー側でセッション管理が不要
- スケーラビリティが高い
-
標準化
- OAuth 2.0 の標準仕様
- 多くのライブラリやフレームワークでサポート
-
柔軟性
- マイクロサービス間の認証に適している
- モバイルアプリやSPAで使いやすい
-
クロスドメイン対応
- CORS の制約を受けにくい
欠点
-
XSS 攻撃のリスク
- JavaScript からアクセス可能な場所に保存すると危険
-
トークンのサイズ
- JWT は比較的大きいため、毎回のリクエストでオーバーヘッド
-
トークンの無効化が難しい
- ステートレスなため、トークンを即座に無効化することが困難
- ブラックリストの実装が必要
-
有効期限管理
- リフレッシュトークンの仕組みが必要
まとめ
- Bearer 認証は HTTP Authorization ヘッダーを使用したトークンベース認証
- JWT が最も一般的なトークン形式
- XSS 対策のため、トークンは LocalStorage ではなくメモリで管理
- HTTPS は必須
- アクセストークンは短時間、リフレッシュトークンで更新する設計が推奨