14
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

「ログインできる」と「操作できる」は、全く別の話です。

認証(Authentication)と認可(Authorization)は、別の処理です。

混ぜた瞬間に、設計は壊れます。

認証と認可を混ぜるのは、

「本人確認できたから、金庫も開くでしょ?」

と言っているのと同じです。
世の中、そんなに親切にはできていません。

よくある会話:

「ログインできたのに、操作できないんだけど?」
→ それ、認証は成功していて、認可で落ちています。

この記事で一貫して伝える主張はこれです:

認証と認可は「順番も責任も目的も完全に別物」

  • 似てるから混ざる
  • 混ざるから設計が壊れる
  • 壊れるとセキュリティ事故になる

この記事では、認証と認可の違いから実務での事故例、正しい実装方法まで、厳しく・詳しく解説します。

認証とは何か(定義を曖昧にしない)

厳密な定義

認証(Authentication)とは:

「あなたは誰か」を確認すること

認証が答える質問は1つだけ

❓ Who are you?

具体例

認証の手段には以下があります:

  • ID / パスワード: 最も一般的な認証方法
  • OAuthログイン: Google、GitHubなどでのログイン
  • 生体認証: 指紋、顔認証
  • APIキー: サービス間通信での本人確認
  • JWTの署名検証: トークンが改ざんされていないか確認(※中身はまだ見ない)
  • 多要素認証(MFA): パスワード + SMSコードなど

認証の責任範囲

認証は「本人確認」で終わりです。

  • ✅ このユーザーは確かにuser_id: 123である
  • ❌ このユーザーが何をしていいかは認証では判断しない

認可とは何か(ここで切り分け)

厳密な定義

認可(Authorization)とは:

「その人が何をしていいか」を決めること

認可が答える質問

❓ What can you do?

具体例

認可の判断には以下があります:

  • 管理者だけが削除できる: role === "admin"
  • 自分のデータだけ見られる: user_id === resource.owner_id
  • GETはOK、DELETEは禁止: HTTPメソッドベースの制御
  • /adminはrole=adminのみ: パスベースの制御
  • リソース単位の権限: 記事Aは編集可、記事Bは閲覧のみ

認可の責任範囲

ログインしていても、禁止は禁止です。

  • ✅ 認証済みユーザーである
  • ✅ しかしこの操作は権限がない → 403 Forbidden

一瞬で分かる対比表

項目 認証(Authentication) 認可(Authorization)
英語 Authentication Authorization
目的 誰かを確認 何ができるか
質問 Who are you? What can you do?
実行タイミング 最初 認証の後
失敗時 ログイン不可(401) 権限エラー(403)
パスワード確認 管理者チェック
保証するもの 本人性 権限

この表を頭に入れるだけで、混同は激減します。

処理の順番(絶対に外せない)

認証と認可には明確な順番があります。

正しい順番

認証 → 認可 → 処理実行

フロー図で理解する

強調ポイント

  1. 認証に成功しても、認可で落ちるのは正常
  2. 認可を先にやることはできない(誰か分からないから)
  3. 認証なしで認可はありえない(誰に権限を与えるかが不明)

よくある「ごっちゃ事故」

実務でよくある失敗パターンを紹介します。

事故① 「ログインできたから全部OKだと思った」

ログインできた ≠ 管理者

間違った実装:

// 認証だけして、認可をしていない
app.delete('/api/users/:id', (req, res) => {
  if (!req.user) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  
  // 認証済みなら誰でも削除できてしまう!
  deleteUser(req.params.id);
  res.json({ success: true });
});

正しい実装:

app.delete('/api/users/:id', (req, res) => {
  // 認証チェック
  if (!req.user) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  
  // 認可チェック
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  deleteUser(req.params.id);
  res.json({ success: true });
});

事故② 認可をフロントだけでやる

フロントでボタン消したから大丈夫

全然大丈夫じゃない

間違った実装:

// フロント側
{user.role === 'admin' && <DeleteButton />}
// API側(認可チェックなし)
app.delete('/api/users/:id', (req, res) => {
  deleteUser(req.params.id);
  res.json({ success: true });
});

問題点:

  • API直叩きで誰でも削除できる
  • ブラウザの開発者ツールで簡単に突破される
  • フロントの制御は「UI/UX」であって「セキュリティ」ではない

正しい方針:

  • フロント: UI制御のみ(ボタンを表示/非表示)
  • バックエンド: 必ず認可チェックを実装

なお、この実装は
「ドアは閉めたけど、壁は壊れている」状態です。

UIは目隠しであって、鍵ではありません。

事故③ JWT = 認可だと思っている

誤解:

JWTがあれば権限チェック不要

現実:

  • JWTは認証情報の運搬手段
  • 中身(claims)を見て判断するのが認可
  • トークン ≠ 権限

比喩で理解する:

JWTを検証しただけでは「名札を見ただけ」。鍵を渡すかどうかは、まだ決めていません。
JWTは「通行証」ではありません。「身分証」です。

マイナンバーを見せたところで、その部屋へ入る権限がないのなら入ることは出来ません
本人確認ができた事実と、その場所に入っていいかどうかは、完全に別問題です。

「でも本人なんですけど?」 は、システムには一切通用しません。

実装例:

// JWTの検証 = 認証
const decoded = jwt.verify(token, SECRET_KEY);
// → { user_id: 123, role: 'user' }

// 中身を見て判断 = 認可
if (decoded.role !== 'admin') {
  return res.status(403).json({ error: 'Forbidden' });
}

実務での具体例(API視点)

認証の例

GET /api/profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

サーバー側:

// 署名検証 = 認証
const user = verifyToken(req.headers.authorization);
if (!user) {
  return res.status(401).json({ error: 'Unauthorized' });
}
// 認証OK: このユーザーは user_id: 123 である

認可の例

// 認可チェック
if (user.role !== 'admin') {
  return res.status(403).json({ error: 'Forbidden' });
}

// または
if (user.id !== resource.owner_id) {
  return res.status(403).json({ error: 'Forbidden' });
}

同じ場所で書かれても、役割は別です。

「認証も認可もやってくれる仕組み」は存在しない

フレームワークは「処理」を助けます。「判断」は絶対にしてくれません。

「roleを発行してくれる」 ことと、 「そのroleで何を許可するか」 は別問題です。

以下のサービスは認証基盤です:

  • OAuth / OpenID Connect: 認証プロトコル
  • Auth0: 認証SaaS
  • Firebase Authentication: Googleの認証サービス
  • AWS Cognito: AWSの認証サービス

これらは以下を提供します:

  • ✅ ユーザー登録・ログイン
  • ✅ トークン発行
  • ✅ 本人確認

しかし、以下は提供しません:

  • ❌ 「このユーザーが管理者かどうか」の判断
  • ❌ 「このリソースを編集できるか」の判断
  • ❌ ビジネスロジックに基づく権限制御

認可ロジックは自分で書く必要があります。

実装パターン: 認証と認可の分離

パターン1: ミドルウェアで分離

// 認証ミドルウェア
const authenticate = (req, res, next) => {
  const user = verifyToken(req.headers.authorization);
  if (!user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  req.user = user;
  next();
};

// 認可ミドルウェア
const requireAdmin = (req, res, next) => {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
};

// 使用例
app.delete('/api/users/:id', authenticate, requireAdmin, (req, res) => {
  deleteUser(req.params.id);
  res.json({ success: true });
});

パターン2: デコレーターで分離(NestJS等)

@Controller('users')
@UseGuards(AuthGuard) // 認証
export class UsersController {
  
  @Delete(':id')
  @Roles('admin') // 認可
  async deleteUser(@Param('id') id: string) {
    await this.usersService.delete(id);
    return { success: true };
  }
}

パターン3: リソースベース認可(ABAC)

const canDeleteUser = (actor, target) => {
  // 管理者は誰でも削除可能
  if (actor.role === 'admin') return true;
  
  // 自分自身は削除可能
  if (actor.id === target.id) return true;
  
  // それ以外は不可
  return false;
};

app.delete('/api/users/:id', authenticate, async (req, res) => {
  const targetUser = await getUser(req.params.id);
  
  if (!canDeleteUser(req.user, targetUser)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  deleteUser(req.params.id);
  res.json({ success: true });
});

HTTPステータスコードの使い分け

認証と認可の失敗は、異なるステータスコードで返します。

状況 ステータスコード 意味
認証失敗 401 Unauthorized 本人確認できなかった
認可失敗 403 Forbidden 本人は確認できたが権限がない
未ログイン 401 Unauthorized トークンがない・無効

なぜこの区別が重要か

401と403を正しく分けることで:

  • フロント: 「再ログインすべきか」を判断できる
  • ログ: 「攻撃か誤操作か」を区別できる
  • 運用: 問題の切り分けが正確になる

よくある間違い:

// ❌ 認可失敗なのに401を返す
if (user.role !== 'admin') {
  return res.status(401).json({ error: 'Unauthorized' });
}

// ✅ 正しくは403
if (user.role !== 'admin') {
  return res.status(403).json({ error: 'Forbidden' });
}

401と403の使い分け:

  • 401: 「誰だか分からない」 → 再ログインで解決する可能性
  • 403: 「誰だか分かるが権限がない」 → 再ログインしても解決しない

よくある誤解まとめ

❌ 認証 = ログイン後の全部

違います。 ログインはスタート地点です。

認証は「誰か」を確認するだけ。「何ができるか」は認可で判断します。

❌ 認可はオプション

違います。 認可はセキュリティそのものです。

認証だけでは「誰でも何でもできる」状態になります。

❌ 権限は画面制御でOK

違います。 APIで必須です。

フロントの制御は簡単にバイパスされます。セキュリティはバックエンドで実装します。

❌ JWTがあれば認可不要

違います。 JWTは認証情報の運搬手段です。

JWTの中身(role、permissionsなど)を見て、認可判断を行う必要があります。

❌ ログインできれば全部見られる

違います。 認証と認可は別です。

ログイン後も、各操作・各リソースごとに権限チェックが必要です。

まとめ

認証は「名乗らせる」だけ。認可は「鍵を渡すかどうか」。

名乗っただけで、金庫は開きません。

システムは感情を汲み取りません。
正しい順番で、正しい判断をしたときだけ、正しく動きます。

覚えておくべき原則

  1. 認証 ≠ 認可(完全に別物)
  2. 認証 → 認可 → 処理(順番は固定)
  3. 認証はログイン、認可は権限(役割が違う)
  4. フロントは見せない、バックエンドは通さない(防御の場所が違う)
  5. 401は本人不明、403は権限なし(エラーの意味が違う)

実装前チェックリスト

設計・実装時に必ず確認すべき項目:

  • この処理は「誰か」を確認しているか?(認証)
  • この処理は「何をしていいか」を判断しているか?(認可)
  • 401と403は意図通りに分かれているか?
  • フロントだけで完結していないか?(APIでも必ず認可)
  • JWTの検証と権限チェックを混同していないか?
  • 認証なしで認可を試みていないか?(順番の確認)

認証と認可の関係


認証と認可を混ぜると、仕様も責任も曖昧になります。

曖昧なセキュリティは、必ず破られます。

この記事が、あなたのセキュリティ設計の一助となれば幸いです。

次に学ぶべきトピック

さらに深く学びたい方は、以下のトピックを探求してみてください:

  • OAuth / OpenID Connectの仕組み: 認証プロトコルの標準
  • JWTの「何が認証で何が認可か」: トークンの中身と使い方
  • RBAC vs ABAC: ロールベース vs 属性ベース認可
  • 403と401の実装パターン: エラーハンドリングのベストプラクティス
  • 権限管理の設計パターン: ACL、RBAC、ABACの選び方

おすすめ学習順:

  1. OAuth / OpenID Connectの仕組み
  2. JWTの役割分担(認証と認可)
  3. RBAC vs ABAC
  4. 401 / 403 の実装設計
  5. 権限管理パターン(ACL等)
14
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?