メインコンテンツまでスキップ

Bearer 認証

Bearer 認証は、HTTP Authorization ヘッダーを使用してトークンベースの認証を行う仕組みです。

Bearer 認証とは

Bearer 認証は、RFC 6750 で定義されている OAuth 2.0 のアクセストークンを HTTP リクエストで送信する標準的な方法です。

基本的な仕組み

  1. クライアントが認証サーバーからアクセストークンを取得
  2. 以降のAPIリクエストで、そのトークンを Authorization ヘッダーに含めて送信
  3. サーバーがトークンを検証してリクエストを処理
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 認証の利点と欠点

利点

  1. ステートレス

    • サーバー側でセッション管理が不要
    • スケーラビリティが高い
  2. 標準化

    • OAuth 2.0 の標準仕様
    • 多くのライブラリやフレームワークでサポート
  3. 柔軟性

    • マイクロサービス間の認証に適している
    • モバイルアプリやSPAで使いやすい
  4. クロスドメイン対応

    • CORS の制約を受けにくい

欠点

  1. XSS 攻撃のリスク

    • JavaScript からアクセス可能な場所に保存すると危険
  2. トークンのサイズ

    • JWT は比較的大きいため、毎回のリクエストでオーバーヘッド
  3. トークンの無効化が難しい

    • ステートレスなため、トークンを即座に無効化することが困難
    • ブラックリストの実装が必要
  4. 有効期限管理

    • リフレッシュトークンの仕組みが必要

まとめ

  • Bearer 認証は HTTP Authorization ヘッダーを使用したトークンベース認証
  • JWT が最も一般的なトークン形式
  • XSS 対策のため、トークンは LocalStorage ではなくメモリで管理
  • HTTPS は必須
  • アクセストークンは短時間、リフレッシュトークンで更新する設計が推奨

参考リンク