Webアプリにおける認証方式の比較と実装

Web開発アーキテクチャの選択

現代のWeb開発には主に二つのアプローチがある。

  • サーバーサイドレンダリング(SSR):サーバー側で動的にHTMLを生成し、クライアントに完全なページを送信する方式。初期表示が速く、検索エンジン最適化(SEO)に有利。
  • フロントエンド・バックエンド分離:バックエンドはデータを提供するAPIのみを提供し、フロントエンドはAjax経由でデータ取得を行う方式。開発効率が高く、ユーザー体験も優れている。

各アーキテクチャの特徴

項目SSR分離型
初期表示速度高速遅い(データ取得後)
SEO対応良好制限あり(ただしSSR対応フレームワークで解決可能)
開発効率
サーバー負荷

両方の利点を組み合わせる戦略として、「トップページはサーバーサイドレンダリング、以降はクライアントサイドで動的処理」というハイブリッド方式が推奨される。

認証方式の選定

ユーザーの身元を確認する仕組みは、開発スタイルによって異なるベストプラクティスが存在する。

  • SSR環境:Sessionベースの認証が適している。ブラウザのCookieとサーバー上のセッションストアを連携して状態管理。
  • 分離型アーキテクチャ:JWT(JSON Web Token)を用いた無状態認証が主流。トークンをクライアントに保持させ、リクエスト毎に送信することで認証を維持。

Session認証の仕組み

HTTPプロトコルは「状態なし」(stateless)であるため、複数のリクエスト間で情報を保持できない。これを克服するために、CookieSessionの組み合わせが使われる。

  • クライアントがログインすると、サーバーはユーザー情報をメモリに保存し、一時的なセッションIDをSet-Cookieヘッダーで返却。
  • その後のリクエストでは、ブラウザが自動的にCookieを付加し、サーバーはそのセッションIDからユーザー情報を復元。

ExpressでのSession設定例

// セッションミドルウェアの導入
const session = require('express-session');
app.use(session({
  secret: 'my_secure_secret_key',
  resave: false,
  saveUninitialized: true
}));

// ログイン処理
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username !== 'admin' || password !== '123456') {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  req.session.user = { username };
  req.session.authenticated = true;
  res.json({ message: 'Login successful' });
});

// 認証済みリソースのアクセス
app.get('/profile', (req, res) => {
  if (!req.session.authenticated) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  res.json({ user: req.session.user });
});

// ログアウト
app.post('/logout', (req, res) => {
  req.session.destroy(err => {
    if (err) return res.status(500).json({ error: 'Logout failed' });
    res.json({ message: 'Logged out successfully' });
  });
});

JWT認証の仕組み

JWTは、サーバーに状態を保持せず、トークン自体にユーザー情報と署名を含むことで、クロスドメインやマイクロサービス環境でも利用可能な認証方式。

  • トークンはHeader.Payload.Signatureの三部構成。
  • 署名はsecretKeyを使って生成され、改ざんを検出可能。
  • クライアントはlocalStorageまたはsessionStorageに保存し、リクエストヘッダーのAuthorization: Bearer <token>形式で送信。

ExpressでのJWT実装

// 必要パッケージのインストール
// npm install jsonwebtoken express-jwt

const jwt = require('jsonwebtoken');
const { expressjwt: auth } = require('express-jwt');

const secretKey = 'my_jwt_secret_123';

// JWTの生成(ログイン時)
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username !== 'admin' || password !== '123456') {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  const token = jwt.sign(
    { username },
    secretKey,
    { expiresIn: '1h' }
  );
  res.json({ token: `Bearer ${token}` });
});

// 認証ミドルウェアの登録
app.use(auth({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/public/] }));

// 保護されたエンドポイント
app.get('/admin/data', (req, res) => {
  // req.auth にユーザー情報が格納されている
  res.json({ data: req.auth, message: 'Access granted' });
});

// エラーハンドリング
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
  res.status(500).json({ error: 'Internal server error' });
});

注意事項

  • パスワードはpayloadに含めない。
  • トークンの有効期限は短めに設定(例:1時間以内)。
  • トークンの削除はクライアント側で行う(サーバーに保存しない)。

タグ: express JWT Session authentication Node.js

9月3日 22:30 投稿