Scope

Authentication Security Notes Jan 6, 2025 JAVASCRIPT

Definition

When you install a new app on your phone, it asks for permissions: access to your camera, location, contacts. You can grant some and deny others. OAuth scopes work exactly the same way for APIs - they let users and applications request specific permissions rather than all-or-nothing access to an account.

Without scopes, authorization would be binary: either an app has full access to your account, or it has no access at all. That is dangerous. A simple photo printing app should not need access to your email, contacts, and financial data just to print some pictures. Scopes solve this by letting you specify exactly what permissions an application needs. When you “Sign in with Google” on a new app, that consent screen listing “See your email address” and “View your profile” is showing you the scopes being requested.

The clever part is that scopes work at multiple levels. First, the application requests specific scopes. Then, the user approves (or denies) them. Finally, the access token issued is limited to only those approved scopes. When the app tries to use the token to access an API, the server checks whether the token’s scopes permit that specific action. A token with read:photos scope cannot delete photos, even if the API supports deletion. This principle of least privilege dramatically reduces risk if a token is ever compromised.

Example

Google “Sign in with” flows: When a website asks you to sign in with Google, it requests specific scopes. A simple login might just ask for profile and email. A calendar app would also request calendar.readonly or calendar.events. Google shows you exactly what access you are granting.

GitHub OAuth apps: When you authorize a GitHub app, you see scopes like repo, user:email, read:org. A CI/CD tool might need repo to read your code, but a notification app only needs notifications - no code access required.

Spotify integrations: Playlist management apps request playlist-modify-public and playlist-modify-private. A music visualization app only needs user-read-playback-state - it cannot modify your playlists, just see what is playing.

Smart home devices: When you link Alexa to your smart thermostat, the scope might be thermostat:read and thermostat:write for temperature control, but not camera:view if Alexa does not need to see your security cameras.

Analogy

The Smartphone App Permissions: This is the most direct analogy because it works identically. Just as your photo app asks for camera access but not your contacts, OAuth scopes let applications request only the API permissions they need. And just like you can revoke app permissions later, you can revoke OAuth scopes.

The Apartment Building Key Card: Your key card might open your apartment, the gym, and the parking garage - but not other apartments or the maintenance room. Each tenant’s card is programmed with specific access. OAuth tokens with scopes work identically - each token is programmed with specific permissions.

The Library Card Categories: Some library cards only let you borrow books. Others also give access to digital resources, meeting rooms, or special collections. The card looks the same, but the permissions encoded differ. OAuth tokens similarly look alike but carry different scopes.

The Employee Access Levels: In a company, different employees have different system access. An accountant can access financial systems but not engineering repositories. A developer has code access but cannot approve expenses. Scopes create these access levels for APIs.

Code Example


// Request specific scopes during authorization
const authUrl = new URL('https://oauth.provider.com/authorize');
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('client_id', 'your_client_id');
authUrl.searchParams.append('redirect_uri', 'https://yourapp.com/callback');
// Request multiple scopes (space-separated)
authUrl.searchParams.append('scope', 'read:user read:email write:posts');
authUrl.searchParams.append('state', generateRandomState());

// Token endpoint returns granted scopes
const tokenResponse = await fetch('https://oauth.provider.com/token', {
  method: 'POST',
  body: tokenParams
});

const { access_token, scope } = await tokenResponse.json();
console.log('Granted scopes:', scope); // May differ from requested

// Validate scopes on API server
function requireScope(requiredScope) {
  return (req, res, next) => {
    const token = extractToken(req);
    const tokenScopes = token.scope.split(' ');

    if (!tokenScopes.includes(requiredScope)) {
      return res.status(403).json({
        error: 'insufficient_scope',
        error_description: `Requires scope: ${requiredScope}`
      });
    }

    next();
  };
}

// Protected route with scope requirement
app.delete('/posts/:id', requireScope('delete:posts'), (req, res) => {
  // Only tokens with delete:posts scope can access
});

// Common scope patterns
// read:resource, write:resource, delete:resource
// resource.read, resource.write, resource.delete
// user:email, user:profile, repo:public, repo:private

Diagram

flowchart TD
    subgraph Request["1. Authorization Request"]
        A[App requests scopes:
read write delete] end subgraph Consent["2. User Consent Screen"] B["This app wants to:
- Read your data
- Write your data
- Delete your data"] C{User Decision} end subgraph Approval["3. User Approves Subset"] D["User grants only:
read write
(denies delete)"] end subgraph Token["4. Token Issued"] E"[Access Token created with:
scope = 'read write'"] end subgraph API["5. API Access Control"] F["GET /data
Requires: read"] G["POST /data
Requires: write"] H["DELETE /data
Requires: delete"] I{Check Token Scopes} end A --> B B --> C C -->|Partial Approval| D C -->|Deny All| J[No Token Issued] D --> E E --> I I -->|Has 'read'| F I -->|Has 'write'| G I -->|Missing 'delete'| K["403 Forbidden
insufficient_scope"] F --> L[200 OK] G --> L H --> I style K fill:#ffcccc style L fill:#ccffcc style D fill:#ffffcc

Security Notes

SECURITY NOTES

CRITICAL: OAuth 2.0 scopes define permissions. Request minimum required scopes.

Scope Design:

  • Granular scopes: Define scopes for specific permissions
  • Hierarchical: Use naming convention indicating hierarchy (user:read, user:write)
  • Purpose-based: Name scopes after what they allow, not how
  • Least privilege: Each scope grants minimum necessary permission
  • Composable: Scopes can be combined for greater access

Scope Examples:

  • read: user: Can read user profile
  • write: user: Can modify user profile
  • admin: Full administrative access
  • offline_access: Can use refresh tokens
  • openid profile email: OpenID Connect profile information

Scope Validation:

  • Verify scopes: Check user has required scope before operation
  • Scope limits: Enforce scope restrictions on operations
  • Scope inflation: Prevent users from requesting excessive scopes
  • Incremental consent: Request additional scopes as needed
  • Scope revocation: Users can revoke individual scopes

Common Mistakes:

  • Too broad scopes: Single scope grants too much access
  • Too narrow scopes: Users can’t do necessary tasks
  • No scope validation: Accepting requests without validating scopes
  • Scope creep: Scopes become too permissive over time
  • Abuse of admin scope: Using admin scope for regular operations

User Communication:

  • Explain scopes: Show users what access they’re granting
  • Scope naming: Use clear, non-technical names when possible
  • Request justification: Explain why scope is needed
  • Granular consent: Allow users to deny specific scopes
  • Permission management: Let users manage granted permissions

Best Practices:

  • Document scopes: Clearly document all available scopes
  • Consistent naming: Use consistent naming convention
  • Review regularly: Periodically review scope definitions
  • User-centric: Design scopes from user perspective
  • Transparent: Be transparent about what access means

Standards & RFCs