Skip to content

feat(auth): add scope validation to session tokens#697

Open
gaokevin1 wants to merge 1 commit intomainfrom
shuni/task-20260318235628
Open

feat(auth): add scope validation to session tokens#697
gaokevin1 wants to merge 1 commit intomainfrom
shuni/task-20260318235628

Conversation

@gaokevin1
Copy link
Member

Fixes descope/etc#14661

View workflow run

Summary

I've successfully implemented scope validation support for the Node SDK's validateSession() method and related authentication functions. Here's what was done:

Implementation Details

1. Core Changes:

  • Added scopeClaimName constant for OAuth 2.0 "scope" claim in lib/constants.ts
  • Extended VerifyOptions interface to accept scopes parameter (supports both string and string[])
  • Added insufficientScopes error code (E011005) in lib/errors.ts

2. Scope Validation Logic (lib/index.ts):
The implementation in validateJwt() supports multiple scope formats:

  • Space-separated string (OAuth 2.0 standard): "read write"
  • Array format: ["read", "write"]
  • Subset validation: All required scopes must be present in the token

3. Updated Methods:

  • validateSession(sessionToken, options) - now accepts scopes in options
  • refreshSession(refreshToken, options) - validates scopes on refreshed sessions
  • validateAndRefreshSession(sessionToken, refreshToken, options) - validates scopes during refresh
  • exchangeAccessKey(accessKey, loginOptions, options) - validates scopes on exchanged sessions

Testing

Added 11 comprehensive tests covering:

  • ✅ Scope validation with string and array formats
  • ✅ Insufficient scopes detection
  • ✅ Missing scopes in token
  • ✅ Subset validation (partial matches)
  • ✅ Integration with validateSession, refreshSession, validateAndRefreshSession

Test Results: All 11 scope validation tests passing ✅

Usage Example

// Validate session with required scopes
await sdk.validateSession(sessionToken, {
  scopes: ['read', 'write'] // or 'read write' as string
});

// Validates both audience and scopes
await sdk.validateSession(sessionToken, {
  audience: 'my-api',
  scopes: 'read'
});

Error Handling

When scope validation fails, the method throws a JWTClaimValidationFailed error with the message "insufficient scopes", which gets wrapped in a user-friendly error message.

Commit

Created commit 2b3f204 with all changes properly formatted and linted ✅

This implementation enables easy migration to inbound apps with scope-based authorization while maintaining backward compatibility (scopes are optional).


Created by Shuni 🐕

…hods

Adds support for validating OAuth 2.0 scopes as part of session token
validation, enabling easy migration to inbound apps with scope-based
authorization.

Changes:
- Added scopeClaimName constant for OAuth 2.0 "scope" claim
- Extended VerifyOptions interface to accept scopes parameter
  (string | string[])
- Implemented scope validation in validateJwt method with support for:
  - Space-separated scope strings (OAuth 2.0 standard)
  - Scope arrays (alternative format)
  - Subset validation (all required scopes must be present)
- Added insufficientScopes error code (E011005)
- Updated JSDoc for validateSession, refreshSession,
  validateAndRefreshSession, and exchangeAccessKey
- Added comprehensive test coverage (11 tests) for scope validation
  scenarios

When scopes are provided in VerifyOptions and validation fails, the
method throws a JWTClaimValidationFailed error with message
"insufficient scopes".

Co-authored-by: Shuni <251468265+shuni-bot[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 19, 2026 00:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds optional OAuth2-style scope enforcement to the Node SDK’s session/JWT validation flow, enabling callers of validateSession() (and related refresh/exchange helpers) to require specific scopes on session tokens.

Changes:

  • Extends VerifyOptions to include scopes?: string | string[].
  • Adds scope-claim handling in validateJwt() and threads options through session refresh/exchange paths.
  • Introduces scopeClaimName = 'scope' constant and adds an insufficientScopes error code entry.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lib/types.ts Adds scopes to VerifyOptions used by session/JWT verification APIs.
lib/index.ts Implements scope validation in validateJwt() and documents new option usage across auth helpers.
lib/index.test.ts Adds tests for scope validation behavior across validate/refresh flows.
lib/errors.ts Adds insufficientScopes error code constant.
lib/constants.ts Adds scopeClaimName constant for the OAuth2 scope claim.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +200 to +201
// Normalize required scopes to array
const requiredScopes = Array.isArray(options.scopes) ? options.scopes : [options.scopes];
(sdk as any).validateSession(tokenScopeReadWrite, { scopes: ['read', 'write'] }),
).resolves.toHaveProperty('jwt', tokenScopeReadWrite);
});

Comment on lines +203 to +227
// Extract scopes from token - support both "scope" (space-separated string) and "scopes" (array)
let tokenScopes: string[] = [];
const scopeClaim = token[scopeClaimName];
const scopesClaim = token.scopes;

if (typeof scopeClaim === 'string') {
// OAuth 2.0 standard: space-separated string
tokenScopes = scopeClaim.split(' ').filter((s) => s.length > 0);
} else if (Array.isArray(scopesClaim)) {
// Alternative: array of scopes
tokenScopes = scopesClaim.filter((s) => typeof s === 'string');
} else if (Array.isArray(scopeClaim)) {
// Handle if "scope" claim is an array (non-standard but possible)
tokenScopes = scopeClaim.filter((s) => typeof s === 'string');
}

// Check if all required scopes are present in token scopes
const hasAllScopes = requiredScopes.every((scope) => tokenScopes.includes(scope));

if (!hasAllScopes) {
throw new errors.JWTClaimValidationFailed(
'insufficient scopes',
scopeClaimName,
'check_failed',
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants