認証(Authentication)とは
日本語の「認証」
広辞苑での「認証」に関する記載をもとに、以下のように分類できます:
引用元: 認証を整理する - IIJ Engineers Blog
そのほかの例:
- 認証局(CA)によるドメインの所有者へのSSL/TLS証明書の発行
(Certification) - Webサイト訪問者がサーバーの提供するDV証明書(※1)をブラウザで確認することによる「表示されているドメインの所有者が本物である(ドメインが認証済みである)」ことの確認(Identity Proofing)
※1 SSL証明書の種類
英語の「Authentication」
「ユーザ認証」とは、「ユーザの識別」と「身元の検証」
- システムを利用しようとしているユーザを、システムに登録済みの「ユーザかどうか識別」し、(= ユーザ名やメールアドレス等による)
- ユーザが主張する「身元を検証」する(= パスワード等による)
※ 実際はユーザ認証のプロセスに「ユーザの有効/無効状態の確認」、「検証に成功した場合の身元の保証(アクセストークンの発行等)」なども含まれる。
簡単に言えば、「あなたは誰ですか?」 という問いに答えるプロセス。
認証の多要素
単要素認証
1つの認証要素のみを使用する方式(例:パスワードのみ)
多要素認証(MFA)
以下の3つのカテゴリーから2つ以上を組み合わせる方式:
-
記憶情報(Something you know)
例:パスワード、PINコード、秘密の質問 -
所持情報(Something you have)
例:スマートフォン、ハードウェアトークン、ICカード -
生体情報(Something you are)
例:指紋、顔認証、虹彩認証
※ 「2段階認証」は(おそらく)日本でのみ使われる方言
※ 同分類の認証要素を2つ使う場合は単要素認証となり、セキュリティ強度的にはあまり意味がない
現代的な認証方式
パスワードレス認証
- パスワードを使わずに本人確認を行う方式
- パスワードの脆弱性(使い回し、忘却、漏洩)を根本的に解決
WebAuthn / FIDO2
- 公開鍵暗号を使用し、秘密鍵はユーザーのデバイスから出ない
- 指紋、顔認証、PINコード、セキュリティキー(YubiKeyなど)などによる方法。
- メリット: フィッシング耐性があり、最も安全な認証方式の一つ
- デメリット: 対応デバイスが必要、実装がやや複雑
- 使用例: GitHub、Google、Microsoftアカウント
マジックリンク
- メールアドレスに一時的な認証リンクを送信
- メリット: 実装が簡単、ユーザーにとって直感的
- デメリット: メールの到達性に依存、メールアカウントのセキュリティに依存
- 使用例: Slack、Medium、Notion
OTP(ワンタイムパスワード)
- SMS OTP: 電話番号にコードを送信
- TOTP: Google Authenticatorなどのアプリでコードを生成
- メリット: 広く普及している、理解しやすい
- デメリット: SIM スワップ攻撃のリスク(SMS)、時刻同期が必要(TOTP)
SSO(Single Sign-On)
- 一度の認証で複数のサービスにアクセスできる仕組み
- ユーザーの利便性向上と、認証の一元管理を実現
- クッキーに依存しない(クロスドメインでも使える)OAuth 2.0 / OpenID Connectプロトコルによって実現される
重要な用語
-
サービスプロバイダ(SP)
- ユーザーが実際に利用したいサービスやアプリケーション
- 認証機能を IdP に委託している
- 例:社内の勤怠管理システム、CRM、プロジェクト管理ツール
-
アイデンティティプロバイダ(IdP)
- ユーザーの認証を行い、身元情報を管理・提供するサービス
- 複数のSPに対して認証サービスを提供
- 例:Okta、Auth0、Azure AD、Google Workspace
関係性の例:
ユーザー「Slackにログインしたい」、「Googleでログイン」を選択
↓
Slack(SP)「認証はGoogle(IdP)にお任せします」
↓
Google(IdP)「ユーザーを認証しました。この人は本物です」
↓
Slack(SP)「確認しました。ログインをどうぞ」
→ 他のサービスでも同じGoogleアカウントで即ログインできる(SSO)
SAML 2.0
- 主に企業向け(B2B)
- XML ベース、成熟した標準規格
- メリット: 企業のセキュリティポリシーを一元管理できる
認証フロー:
ユーザーがSP(例:Salesforce)にアクセス
↓
SPがユーザーをIdP(例:企業のAzure AD)にリダイレクト
↓
IdPでユーザーが認証(社員番号とパスワードなど)
↓
IdPが認証結果(SAMLアサーション)をSPに送信
↓
SPがユーザーのアクセスを許可
OAuth 2.0 / OpenID Connect
- OAuth 2.0: 認可のためのプロトコル(「何ができるか」)
- OpenID Connect: OAuth 2.0上に構築された認証レイヤー(「誰であるか」)
- コンシューマー向けサービス(B2C)
- 使用例: 「Googleでログイン」「GitHubでログイン」機能
重要な用語
-
アクセストークン
- APIへのアクセス権限を表すトークン
- スコープ(権限範囲)、有効期限などを持つ
- 使用例: 「このトークンを持っている人は、ユーザーのプロフィール情報を読む権限がある」
- 寿命: 通常は短時間(15分〜1時間)
- 形式: JWTまたは不透明な文字列
-
IDトークン
- ユーザーの身元情報を含むトークン
- ユーザーID、メールアドレス、名前、認証時刻などを持つ
- 使用例: 「このユーザーは確かにGoogleで認証されたuser@example.comです」
- 寿命: アクセストークンと同程度
-
リフレッシュトークン
- 新しいアクセストークンを取得するためのトークン
- 使用例: アクセストークンの有効期限が切れた際に、再認証なしで新しいトークンを取得
- 寿命: 長期間(数日〜数ヶ月)
- セキュリティ: 最も厳重に管理すべきトークン
-
JWT
- 認証情報を含む自己完結型のトークン。
- OIDCではJWT(IDトークン)が必須、OAuth 2.0でもアクセストークンとしてJWTがしばしば使われる。
- サーバー側でセッション管理が不要。
- 構造:
- Header: トークンのタイプと署名アルゴリズム
- Payload: ユーザー情報やクレーム(権限など)
- Signature: 改ざん防止のための署名
- メリット:
- ステートレス(サーバー側でセッション管理不要)
- マイクロサービス間での認証情報共有が容易
- モバイルアプリとの相性が良い
- 注意点:
- トークンの無効化が難しい(有効期限まで有効)
- トークンサイズが大きい
- ローカルストレージ保存はXSS攻撃のリスク
認証フロー:
ユーザーがアプリ(SP)で「Googleでログイン」をクリック
↓
GoogleのログインページIdP)にリダイレクト
↓
ユーザーがGoogleアカウントで認証
↓
アプリが以下を受け取る
・IDトークン: ユーザーが誰であるかの証明
・アクセストークン: Google APIを呼び出す権限
・リフレッシュトークン(オプション): トークンを更新する権限
↓
アプリがアクセストークンを使ってユーザー情報を取得
セッションベース認証 vs トークンベース認証
項目 | セッションベース | トークンベース(JWT) |
---|---|---|
状態管理 | サーバー側(ステートフル) | クライアント側(ステートレス) |
スケーラビリティ | セッション共有が必要 | 高い |
無効化 | 即時可能 | 有効期限まで有効 |
実装の複雑さ | 比較的シンプル | やや複雑 |
ユースケース | 従来型Webアプリ | SPA、モバイル、マイクロサービス |
セキュリティのベストプラクティス
- 多要素認証(MFA)の導入:特に管理者や特権ユーザーに必須
- アカウント回復手段の用意:パスワードレス環境でも復旧ルートを準備
- レート制限の実装:ブルートフォース攻撃対策として有効
- 監査ログの記録:すべての認証イベントを追跡可能に
- 定期的な見直し:不要な認証方式・トークンを削除・更新
認可(Authorization)とは
日本語の「認可」
1 適当と認めて、許可すること。
2 公の機関(行政庁)が第三者の行為を補充して、その法律上の効力を完成させる行政行為。「保育所の設立を—する」→許可
「認可」の用法 | 対応する英語 | 具体例 |
---|---|---|
公の機関による許可 | Approval | 建築許可、営業許可など |
特定の行為に対する権限の付与 | Authorization | ファイルアクセス権限の付与 |
リソースへのアクセス制御 | Access Control | アクセス制御リスト(ACL)による制限 |
そのほかの例:
- プロジェクトの予算を上司が承認する(Approval)
- APIキーを使って、特定のユーザだけがAPIを利用できるように設定する(Authorization)
- 会社のオフィスに入るために社員証をスキャンし、許可された区域にのみアクセスできる(Access Control)
英語の「Authorization」
「ユーザ認可」とは、「アクセス権の確認」と「リソースの制限」
- システムを利用しようとしているユーザが、システム内の特定のリソースや機能にアクセスする権限を持っているかどうかを確認し、
- 適切なアクセス制限を適用する
※ 実際はユーザ認可のプロセスに「アクセスレベルの適用」、「アクセス記録の保持」、「ポリシーの適用」なども含まれる。
※ ISO/IEC 27001:2013 では、認可は情報セキュリティ管理システムの一部として、組織の情報資産を保護するためのアクセス制御の手段と定義されている。
簡単に言えば、「あなたは何ができますか?」 という問いに答えるプロセス。
認可の三大要素
認可は様々な場所で様々な角度から適用され、以下の要素を繰り返し問い続けます:
-
誰がリクエストしているか(actor)
例:ユーザ、サービスアカウント、システムプロセスなど -
何をしようとしているのか(action)
例:読み取り、書き込み、削除、実行など -
何にしようとしているのか(resource)
例:ファイル、データベース、APIエンドポイントなど
認可の実装パターン
認可はどこで適用できるか?
Webアプリケーションでは、以下の複数の層で認可を実装できる:
-
リクエストの入り口部分(ロードバランサー)
- IPアドレス制限など
-
Proxy部分(API Gateway や Nginx)
- トークン、HTTPリクエスト、パス
-
WebアプリのRouter部分
- Userオブジェクトに
isAdmin: true
があるなら、/admin
関連のパスへのアクセスを許可
- Userオブジェクトに
-
WebアプリのController部分
- すべての情報を使って実際の認可を行う
-
Webアプリのデータアクセスの部分
- resource の絞り込みが可能
認可のインターフェース
-
Decision(認可の判断)
- actor, action, resource に基づいて、true or falseを返す関数やメソッド
-
Enforcement(認可の適用)
- Decisionに基づいて、データ取得時のフィルターを追加したり、HTTPレスポンスで403 Forbiddenを返すなどの具体的な行動を取る部分
また、認可ロジックはビジネスロジックから分離すべき。
実装例(アンチパターン)
// △ ビジネスロジックと認可ロジックが密結合
function deletePost(user, post) {
if (user.role !== 'admin' && user.id !== post.authorId) {
throw new Error('Not authorized');
}
postService.delete(post);
}
実装例(推奨パターン)
// ◯ ビジネスロジックと認可ロジックが疎結合
const policy = new Policy();
const authorizer = new Authorizer(policy);
function deletePost(user, post, authorizer) {
if (!authorizer.isAllowed(user, 'delete', post)) {
throw new Error('Not authorized');
}
postService.delete(post);
}
// Authorizerインターフェイスによって認可ロジックが分離・再利用可能
// Authorizer.js(認可の適用インターフェイス)
class Authorizer {
constructor(policy) {
this.policy = policy;
}
isAllowed(user, action, resource) {
return this.policy.evaluate(user, action, resource);
}
}
// Policy.js(認可ルールの実装・集約)
class Policy {
evaluate(user, action, resource) {
switch (resource.type) {
case 'post':
return this.evaluatePost(user, action, resource);
case 'comment':
return this.evaluateComment(user, action, resource);
default:
return false;
}
}
evaluatePost(user, action, post) {
switch (action) {
case 'delete':
case 'edit':
return user.role === 'admin' || user.id === post.authorId;
case 'view':
return post.isPublic || user.role === 'admin' || post.collaborators.includes(user.id);
default:
return false;
}
}
}
認可モデル
RBAC(Role-Based Access Control)
ユーザをロールに割り当て、ロールに権限を付与する。
レベル1:組織のロール
- 各ユーザーを「単一の組織」に関連付け、ロールを割り当てる
- すべてのアクセスは、ユーザーが組織内で持つロールによって制御される
- 「管理者」と「メンバー」の2つのロールから始め、必要に応じて追加する
- 「メンバー」は、アプリケーションのすべてのコア機能にアクセス(読み書き)できる
- 「管理者」は、メンバーが実行できるすべての操作に加え、ユーザーの組織への招待、支払いの設定、設定の変更、組織の削除などの権限を与える
role_permissions = {
"member": ["read:repository", "write:repository"],
"admin": ["read:repository", "write:repository", "add_user:organization"]
}
レベル2:組織間のロール
- 1人のユーザーが「複数の組織」に所属できるようにする。
- ユーザーは、所属する組織ごとにロールが必要になる。
- 1 対多のユーザーと組織の関係を、多対多の関係に変更する必要がある。
レベル3:リソース固有のロール
- 組織に限らず、あらゆるリソースにロールを関連付ける
レベル4:カスタムロール
- ユーザーが独自のロールを作成できるようにする
- ユーザーが独自のロールに権限を割り当てられるようにする
- ユーザーをロールに割り当てることで使用できる
- AWSのIAMのようなシステムに近づく
- 幅広いユースケースをサポートできるが、新機能に対してカスタムロールが意図した動作になるか?など十分な注意が必要になる。
- GitHub では繰り返し延期の上、エンタープライズプランでのみサポート
- GitLabではサポートしていない
ReBAC(Relationship-Based Access Control)
オブジェクト間の関係を記述することで認可ロジックを記述
- ユーザーは自身が作成したコメントを削除できる
-> コメントとユーザーの親子関係 - 親リポジトリの contributor だった場合は issue を読むことができる
-> issue とリポジトリの親子関係 - チームに属していて、チームが contributor だった場合は、リポジトリの contributor となる
-> ユーザーがチームに所属するグループ構造
1. データ所有(Ownership)
- “Issue は作成者が所有し、作成者(owner)だけが特定操作をできる”という自然なモデル。
- テーブルに
owner_id
フィールドを設け、作成時に自動設定。
2. 親子リソース構造
- リポジトリ内のIssueやコメントなどのネスト構造
- 親リソースの権限が子にも及ぶモデル
3. ユーザーグループ(Teams)
- ユーザーをチームにまとめ、チーム権限を通じて複数ユーザーへのアクセス制御を集約
- 大規模組織向けのアクセス管理
4. 階層構造(Recursive Hierarchies)
- チームや組織内での多段階関係(例:管理者→中間管理者→メンバー)
- 組織階層やフォルダ構造などのシナリオで活用
ABAC(Attribute-Based Access Control)
ユーザやリソースの属性に基づいてアクセス権が決定される
- ユーザーの役職、部門
- プロジェクトの状態
- 時間帯、場所などの環境属性
ベストプラクティス
DBで管理 vs コードで管理
DBで管理するメリット:
- 動的変更に適している(再デプロイ不要)
- 管理画面から容易に管理可能
- 監査ログや履歴管理が容易
アプリケーションコードで管理するメリット:
- 複雑なロジックを柔軟に記述できる
- 保守性、テスト容易性、パフォーマンスに優れる
権限や属性などのデータはDBで管理し、認可のルールやロジックそのものはコードで管理する設計が推奨される。
実装例(PBAC:Policy Based Access Control)
-- 権限管理の基本テーブル
CREATE TABLE permissions (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, -- e.g. 'post.edit'
resource_type VARCHAR(100), -- e.g. 'post'
action VARCHAR(100) -- e.g. 'edit'
);
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL -- e.g. 'admin', 'editor'
);
CREATE TABLE role_permissions (
role_id INTEGER REFERENCES roles(id),
permission_id INTEGER REFERENCES permissions(id),
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_roles (
user_id INTEGER REFERENCES users(id),
role_id INTEGER REFERENCES roles(id),
scope_type VARCHAR(100), -- e.g. 'organization', 'project'
scope_id VARCHAR(255), -- e.g. organization_id
PRIMARY KEY (user_id, role_id, scope_type, scope_id)
);
// 権限評価エンジン
class PolicyEngine {
async evaluate(user, action, resource) {
// 1. 所有者チェック
if (resource.ownerId === user.id) {
return true;
}
// 2. ロールベースの権限チェック
const hasPermission = await this.checkRolePermission(
user.id,
resource.type,
action
);
// 3. カスタムルールの適用
if (hasPermission) {
return this.applyBusinessRules(user, action, resource);
}
return false;
}
async checkRolePermission(userId, resourceType, action) {
const result = await db.query(`
SELECT 1 FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1
AND p.resource_type = $2
AND p.action = $3
LIMIT 1
`, [userId, resourceType, action]);
return result.rows.length > 0;
}
applyBusinessRules(user, action, resource) {
// ビジネスルールはコードで管理
if (action === 'edit' && resource.type === 'post') {
// 公開済み記事は管理者のみ編集可能
if (resource.status === 'published') {
return user.roles.includes('admin');
}
// ドラフトは作者と編集者が編集可能
return user.roles.includes('editor');
}
return true;
}
}
// 使用例
const policyEngine = new PolicyEngine();
async function updatePost(req, res) {
const post = await getPost(req.params.id);
const canEdit = await policyEngine.evaluate(
req.user,
'edit',
{
type: 'post',
id: post.id,
ownerId: post.authorId,
status: post.status
}
);
if (!canEdit) {
return res.status(403).json({ error: 'Forbidden' });
}
// ビジネスロジックの実行
await postService.update(post.id, req.body);
res.json({ success: true });
}
まとめ
- 認証は「誰であるか」を確認するプロセス(Webサイトのログインのイメージ)
- 認可は「何ができるか」を制御するプロセス(AWSやGoogle CloudのIAMのイメージ)
- 認可ロジックはアプリケーションロジックとは分離して実装する
- 認可に必要なデータは、そのサービス内(アプリのデータと同じDB)で持つ
- 複数の層で認可を実装し、深層防御を実現する
項目 | 認証(Authentication) | 認可(Authorization) |
---|---|---|
質問 | あなたは誰ですか? | あなたは何ができますか? |
目的 | 身元の確認 | アクセス権限の確認 |
実装例 | ログイン画面 | 管理者のみ表示されるメニュー |
失敗時 | 401 Unauthorized | 403 Forbidden |
参考資料
- 認証を整理する - IIJ Engineers Blog
- 認証と認可の違い | Cloudflare
- Authorization Academy | Oso
- Role-Based Access Control (RBAC) | Oso
- Relationship-Based Access Control (ReBAC) | Oso
- Guide to Attribute Based Access Control (ABAC) | NIST
- 認可のアーキテクチャに関する考察(Authorization Academy IIを読んで) | Zenn
- 認証認可の基礎からはじめる AWS IAM 徹底入門 | SpeakerDeck
- やさしい認証認可 | SpeakerDeck