OAuth2 Scopes Explained
OAuth2 scopes define what an application can do with your data. Many developers misuse them, granting too much access or restricting APIs unnecessarily. Let’s explore how scopes work, why they matter, and best practices for implementing them correctly.
What Are OAuth2 Scopes?
Scopes define permissions for an application. Instead of granting full access, they allow developers to specify what actions an app can perform.
For example, in a calendar API, scopes might look like this:
calendar.read
→ View eventscalendar.write
→ Create & edit eventscalendar.delete
→ Remove events
Rather than giving an application unrestricted access, OAuth2 lets users approve specific actions.
Why Are Scopes Important?
OAuth2 allows users to control what an application can do with their data. When logging into an app, users see a prompt like:
- ✅ Read your contacts
- ❌ Send emails on your behalf
This selective approval mechanism enhances security by limiting the damage if a token is leaked or misused.
How Do Scopes Work in OAuth2?
OAuth2 scopes follow a structured process:
- The client requests specific scopes when redirecting the user to log in.
- The authorization server prompts the user for consent.
- If the user approves, the access token is issued with the granted scopes.
- The API verifies scopes before allowing any action.
When redirecting a user for authentication, scopes are included in the URL:
https://auth.example.com/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_CALLBACK_URL
&scope=openid email profile contacts.read
&state=RANDOM_STRING
Enforcing Scopes in an API
APIs should always check if an access token has the required scopes before executing an action.
Using @edgefirst-dev/jwt
to verify and enforce scope-based access:
import { JWT } from "@edgefirst-dev/jwt";
export async function authenticate(request: Request) {
let token = request.headers.get("Authorization")?.split(" ")[1];
if (!token) throw new Error("Unauthorized");
let jwt = await JWT.verify(token, publicKeys, {
issuer: "auth.example.com",
audience: "api.example.com",
});
if (!jwt.scope.includes("contacts.read")) {
throw new Error("Insufficient permissions");
}
return jwt;
}
This ensures that only tokens with the correct scope can access protected resources.
Best Practices for OAuth2 Scopes
- ✅ Use fine-grained scopes (
contacts.read
instead ofcontacts.full_access
). - ✅ APIs should enforce scope validation, never trust clients.
- ✅ Follow "least privilege" — apps should request only what they need.
- ✅ Group scopes logically (e.g., read/write/delete).
What If Your Authorization Server Doesn't Support Scopes?
If your identity provider doesn’t support OAuth2 scopes, an alternative approach is role-based access control (RBAC):
- Admin: Full access
- User: Read & write
- Guest: Read-only
The API can then check roles instead of scopes when enforcing permissions.
I'm currently writing a book called React Router OAuth2 Handbook, focused on implementing secure OAuth2 authentication in Remix and React Router apps—using patterns you can apply to any web app.
The landing page is live (book coming soon) at books.sergiodxa.com