ASVS 5.0 V10.4.11 (Level 2) requires that the authorization server configuration gives each OAuth client only the scopes it needs. This is least privilege applied to client registration.
Why it matters
Scopes define what a token can do. A reporting dashboard that only reads data should never be able to obtain a write scope, even if its developers ask for one by accident or an attacker edits the request.
How to implement it
Keep an explicit allowed scope list per client and intersect it with whatever the request asks for.
def grant_scopes(client, requested):
requested = set(requested.split())
allowed = set(client.allowed_scopes)
if not requested <= allowed:
raise InvalidScope()
return requested
Avoid wildcard or "all scopes" defaults on registration. Review the list when a client changes, and delete scopes nobody uses.
How to test it
For each client, request a scope outside its list and expect invalid_scope. Export your client configuration and review it: any client with an administrative scope should have a named owner and a reason on record.
Reviewing scope assignments
Run a review whenever you add a client and a scheduled one every quarter. List each client with its scopes and the last time each scope was used. A scope that was granted but never used is a candidate for removal. The audit trail from your authorization server often shows this, and removing unused access costs nothing.
Name scopes by action and resource, such as orders.read, instead of broad names such as admin. Narrow names make it obvious when a client asks for too much.
Common mistakes
- Default scope sets that include everything.
- Copying one client's configuration to create the next.
- Treating consent as a substitute for server side limits.
Quick check
Where is the scope limit enforced?
At the authorization server, against the client's registration, regardless of what the request asks for.