ウェブエンジニア問題集
第8章

認証と認可の設計 — セッション・JWT・OAuth

5
この章の目次開く

「ログイン機能を作って」と言われたとき、認証の方式選定から始められるかどうかは設計力の分かれ目です。 セッション、JWT、OAuth——それぞれの仕組みと使いどころを理解しておかないと、セキュリティ上の事故にもつながります。

この章では、認証と認可の違いから、主要な認証方式の比較までを整理します。

学習者学習者

「認証」と「認可」って同じじゃないの?セッションとJWT、どっちを選べばいいの…?

認証と認可の違い

この2つは混同されがちですが、まったく別の概念です。

概念質問
認証(Authentication)「あなたは誰?」ログイン、本人確認
認可(Authorization)「あなたは何ができる?」管理者のみ削除可能、一般ユーザーは閲覧のみ

認証が通っても、認可されていない操作はブロックする必要があります。

主要な認証方式の比較

方式状態管理適するケース
セッションサーバー側(メモリ/DB/Redis)従来型Webアプリ、SSR中心
JWTクライアント側(トークン)SPA、モバイルアプリ、マイクロサービス
OAuth 2.0外部サービスに委譲ソーシャルログイン、外部API連携

セッション方式の仕組み

セッション方式は、ログイン状態をサーバー側で管理する伝統的な手法です。

// Express + express-session の例
app.post('/login', async (req, res) => {
  const user = await db.findByEmail(req.body.email);
  if (!user || !await bcrypt.compare(req.body.password, user.passwordHash)) {
    return res.status(401).json({ error: '認証失敗' });
  }
  req.session.userId = user.id;
  res.json({ message: 'ログイン成功' });
});
 
app.get('/profile', (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: '未認証' });
  }
  // req.session.userId でユーザー情報を取得
});
typescript

JWTの仕組み

JWT(JSON Web Token)はトークンにユーザー情報を含め、サーバー側でセッションを持たない方式です。

JWTの構造

JWTは3つのパートをドット(.)で繋いだ文字列です。

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyLCJleHAiOjE3MTk5MH0.署名
 ↑ Header              ↑ Payload                               ↑ Signature
パート内容Base64デコードすると
Headerアルゴリズム情報{"alg":"HS256"}
Payloadユーザー情報・有効期限{"userId":42,"exp":1719900000}
Signature改ざん検知用の署名サーバーの秘密鍵で生成
先生先生

Payloadは暗号化されていないので、Base64デコードすれば誰でも中身を読める。パスワードやクレジットカード番号を入れてはいけないよ。

// jsonwebtoken の例
import jwt from 'jsonwebtoken';
 
// トークン生成(ログイン時)
const token = jwt.sign(
  { userId: user.id },
  process.env.JWT_SECRET!,
  { expiresIn: '1h' }
);
 
// トークン検証(リクエストごと)
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
// decoded.userId でユーザーを特定
typescript

リフレッシュトークンの設計

アクセストークンの有効期限は短くし(15分〜1時間)、長期間のログイン維持にはリフレッシュトークンを使います。

リフレッシュトークンはサーバー側のDBに保存し、無効化できるようにしておく。ユーザーがログアウトしたら、そのリフレッシュトークンを削除する。

OAuth 2.0 — 認可コードフロー

OAuth 2.0は、ユーザーのパスワードを預からずに外部サービスの認証を利用する仕組みです。「Googleでログイン」「GitHubでログイン」がこれにあたります。

OAuth認証の仕組みを学ぶ
OAuth 2.0 では、ユーザーのパスワードを自分のサーバーに保存しなくて済む

ロールベースアクセス制御(RBAC)

認可の実装でよく使われるのが RBAC です。ユーザーにロール(役割)を割り当て、ロールごとに操作権限を定義します。

// シンプルなRBACの例
type Role = 'admin' | 'editor' | 'viewer';
 
const permissions: Record<Role, string[]> = {
  admin:  ['read', 'write', 'delete', 'manage_users'],
  editor: ['read', 'write'],
  viewer: ['read'],
};
 
function authorize(userRole: Role, action: string): boolean {
  return permissions[userRole].includes(action);
}
 
// ミドルウェアとして使う
function requirePermission(action: string) {
  return (req, res, next) => {
    if (!authorize(req.user.role, action)) {
      return res.status(403).json({ error: '権限がありません' });
    }
    next();
  };
}
 
app.delete('/posts/:id', requirePermission('delete'), deletePost);
typescript

方式選定のフローチャート

ちゃんと使うためのポイント

  • 認証(誰か)と認可(何ができるか)は別の仕組みとして設計する
  • JWTはステートレスだがトークン無効化が難しい。リフレッシュトークンをDB管理して対応する
  • セッション方式はCSRF対策が必須
  • JWTのPayloadは暗号化されていない。秘密情報を含めない
  • OAuth 2.0はパスワードを預からずに済む。ソーシャルログインに適する
  • 「どの方式が正解か」ではなく、要件(SPA?SSR?モバイル?)で選ぶ

次の章では、認証後のアプリケーション内部の状態管理の設計を見ていきます。