TL;DR(要点)
OAuth 2.0スコープは、アクセストークンが実行できる操作を定義する権限文字列です。`pets:read`や`orders:write`のように`リソース:アクション`形式を使用します。認証時にスコープを要求し、APIエンドポイントでそれらを検証します。Modern PetstoreAPIは、ペット、注文、ユーザーデータへの読み取り/書き込みアクセスにスコープを実装しています。
はじめに
サードパーティ製アプリがペットストアの在庫を読み取りたいと考えています。そのアプリは、注文の作成、ペットの削除、ユーザーの管理に対してフルアクセスを持つべきでしょうか?いいえ、在庫の読み取りのみを行うべきです。
OAuth 2.0スコープがこれを解決します。スコープはアクセストークンが持つ権限を定義します。アプリは`inventory:read`スコープを要求します。あなたのAPIは、データを返す前にトークンがこのスコープを持っていることを検証します。
Modern PetstoreAPIは、ペット、注文、在庫、ユーザーといったすべてのリソースに対して、きめ細かいスコープを実装しています。
OAuth APIをテストしている場合、Apidogはスコープの検証と認可フローのテストに役立ちます。
OAuth 2.0スコープとは?
スコープは、OAuthアクセストークンに含まれる権限文字列です。
スコープの形式
pets:read - ペットデータを読み取る
pets:write - ペットを作成/更新する
orders:read - 注文を読み取る
orders:write - 注文を作成する
admin:all - 完全な管理者アクセス
OAuthフローにおけるスコープ
1. 認可リクエスト:
GET /oauth/authorize?
client_id=app123&
scope=pets:read orders:read&
redirect_uri=https://app.com/callback
2. ユーザーの同意:
アプリ「PetFinder」が以下を求めています:
- あなたのペットを読み取る
- あなたの注文を読み取る
[許可] [拒否]
3. アクセストークン:
{
"access_token": "eyJhbGc...",
"scope": "pets:read orders:read",
"expires_in": 3600
}
4. APIリクエスト:
GET /v1/pets
Authorization: Bearer eyJhbGc...
200 OK (スコープ検証済み)
スコープの仕組み
トークンに含まれるスコープ
アクセストークンには付与されたスコープが含まれます:
// デコードされたJWT
{
"sub": "user-456",
"scope": "pets:read orders:read",
"exp": 1710331200
}
APIがスコープを検証する
app.get('/v1/pets', requireScope('pets:read'), async (req, res) => {
const pets = await getPets();
res.json(pets);
});
app.post('/v1/pets', requireScope('pets:write'), async (req, res) => {
const pet = await createPet(req.body);
res.status(201).json(pet);
});
function requireScope(requiredScope) {
return (req, res, next) => {
const token = extractToken(req);
const decoded = verifyToken(token);
if (!decoded.scope.includes(requiredScope)) {
return res.status(403).json({
error: 'insufficient_scope', // スコープ不足
message: `必要なスコープ: ${requiredScope}`
});
}
next();
};
}
スコープの設計
スコープの命名規則
リソース:アクション パターン:
pets:read
pets:write
orders:read
orders:write
users:read
users:write
きめ細かいスコープ:
pets:read
pets:create
pets:update
pets:delete
ワイルドカードスコープ:
pets:* - すべてのペット操作
*:read - すべてのリソースを読み取る
admin:* - 完全な管理者アクセス
スコープの階層
admin:all
├── pets:*
│ ├── pets:read
│ ├── pets:write
│ └── pets:delete
├── orders:*
│ ├── orders:read
│ └── orders:write
└── users:*
├── users:read
└── users:write
スコープ検証の実装
ミドルウェアアプローチ
function requireScopes(...requiredScopes) {
return (req, res, next) => {
const token = extractToken(req);
const decoded = verifyToken(token);
const tokenScopes = decoded.scope.split(' ');
const hasAllScopes = requiredScopes.every(scope =>
tokenScopes.includes(scope) || tokenScopes.includes('admin:all')
);
if (!hasAllScopes) {
return res.status(403).json({
error: 'insufficient_scope', // スコープ不足
required: requiredScopes,
provided: tokenScopes
});
}
req.user = decoded;
next();
};
}
// 使用例
app.get('/v1/pets', requireScopes('pets:read'), getPets);
app.post('/v1/pets', requireScopes('pets:write'), createPet);
app.delete('/v1/pets/:id', requireScopes('pets:delete'), deletePet);
デコレータアプローチ (TypeScript)
function RequireScopes(...scopes: string[]) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const req = args[0];
const res = args[1];
const token = extractToken(req);
const decoded = verifyToken(token);
if (!hasScopes(decoded.scope, scopes)) {
return res.status(403).json({ error: 'insufficient_scope' }); // スコープ不足
}
return originalMethod.apply(this, args);
};
};
}
// 使用例
class PetsController {
@RequireScopes('pets:read')
async getPets(req, res) {
const pets = await this.petService.findAll();
res.json(pets);
}
@RequireScopes('pets:write')
async createPet(req, res) {
const pet = await this.petService.create(req.body);
res.status(201).json(pet);
}
}
Modern PetstoreAPIでのスコープの使用方法
利用可能なスコープ
pets:read - ペットデータを読み取る
pets:write - ペットを作成/更新する
pets:delete - ペットを削除する
orders:read - 注文を読み取る
orders:write - 注文を作成する
inventory:read - 在庫を読み取る
inventory:write - 在庫を更新する
users:read - ユーザープロフィールを読み取る
users:write - ユーザープロフィールを更新する
admin:all - 完全なアクセス
スコープの検証
GET /v1/pets
Authorization: Bearer token_with_pets:read
200 OK
POST /v1/pets
Authorization: Bearer token_with_pets:read
403 Forbidden (アクセス禁止)
{
"error": "insufficient_scope", // スコープ不足
"required": ["pets:write"], // 必須
"provided": ["pets:read"] // 提供済み
}
Modern PetstoreAPI OAuthドキュメントをご覧ください。
Apidogでのスコープのテスト
ApidogはOAuthスコープのテストをサポートしています:
- OAuth 2.0認証を設定する
- 特定のスコープをリクエストする
- 異なるスコープでエンドポイントをテストする
- スコープ不足による403応答を検証する
ベストプラクティス
1. きめ細かいスコープを使用する - read_allではなくpets:readを使用する
2. 命名規則に従う - リソース:アクション形式
3. すべてのスコープを文書化する - APIドキュメントに一覧表示する
4. すべてのリクエストで検証する - クライアントを信用しない
5. 明確なエラーを返す - 必須スコープと提供スコープを表示する
6. 最小特権を使用する - 必要な最小限のスコープを要求する
結論
OAuth 2.0スコープは、きめ細かいアクセス制御を提供します。リソース:アクション形式を使用し、すべてのリクエストで検証し、すべてのスコープを文書化してください。Modern PetstoreAPIは、本番環境に対応したスコープの実装を示しています。
よくある質問
スコープとロールの違いは何ですか?
スコープはアクセストークンに対する権限です。ロールは、割り当てられた権限を持つユーザーグループです。
複数のスコープを持つことはできますか?
はい、スペースで区切ります: pets:read orders:read users:write
スコープを取り消すにはどうすればよいですか?
アクセストークンを取り消すか、異なるスコープを持つ新しいトークンを発行します。
スコープはJWTに含まれるべきですか?
はい、ステートレスな検証のためにscopeクレームに含めます。
スコープはどの程度きめ細かくすべきですか?
きめ細かさと使いやすさのバランスを取ります。通常、pets:readとpets:writeで十分です。
