Skip to content

Proposal: Separate scopes and permissions in Firewall Rules for Semantic Clarity with JWT Validator #60

@homestar9

Description

@homestar9

The current JwtAuthValidator uses the permissions property to check both JWT token scopes AND the user's hasPermission() method with OR logic. This conflates two distinct authorization concepts and creates semantic ambiguity. I propose introducing a separate scopes property for JWT-based checks while keeping permissions for user-level permission checks.

Current Behavior

When using JwtAuthValidator, firewall rules specify permissions:

{
    "secureList": "v1:admin",
    "permissions": "admin"
}

The validator checks both:

results.allow = (
    tokenHasScopes( arguments.permissions, payload.scope )  // JWT scope
    ||
    variables.cbsecurity.has( arguments.permissions )       // user.hasPermission()
);

The Problem

  1. Semantic Confusion
    In the OAuth2/JWT ecosystem, "scopes" and "permissions" are distinct concepts:

Scopes are token-based claims embedded at creation time (fast, static). Whereas permissions are user/resource-level authorizations (dynamic, often database-backed) Using permissions for both makes it unclear which concept is being validated.

  1. Implicit OR Logic
    The OR operation between scope and permission checks is not obvious from the configuration. Users may expect a variety of outcomes including, but not limited to:
  • Only JWT scopes are checked (since we're using JWT authentication)
  • Only permissions are checked (since the property is called "permissions")
  • AND logic (must have both)
  • The actual behavior (OR with short-circuit) is discovered only by reading source code.
  1. Performance Implications Unclear
    Users don't realize that:
  • Scope checking is fast (reading from decoded token)
  • Permission checking may hit the database on every request
  • The OR means performance varies based on which check succeeds first
  1. Can't Specify Exclusive Checks
    There's no way to say:
  • "Only check JWT scopes" (for maximum performance)
  • "Only check permissions" (for dynamic, real-time authorization)
  • "Check both with AND logic" (scope AND permission required)

Proposed Solution

Introduce a separate scopes property for JWT token validation:

// Check JWT scopes only (fast, static)
{
    "secureList": "v1:admin",
    "scopes": "admin"
}

// Check user permissions only (dynamic, database/entity-backed)
{
    "secureList": "v1:reports",
    "permissions": "view_reports"
}

// Check both with explicit AND logic
{
    "secureList": "v1:sensitive",
    "scopes": "admin",           // Must have admin scope in JWT
    "permissions": "delete_all"  // AND have delete_all permission
}

Benefits

Semantic Clarity: Property names match industry-standard terminology
Explicit Intent: Configuration clearly shows what's being validated
Performance Visibility: Users can choose fast (scopes) vs dynamic (permissions)
Flexibility: Can use one, the other, or both with AND/OR logic
Better Documentation: Each property has a single, clear purpose
Easier Adoption: New users familiar with OAuth2/JWT understand immediately

Backward Compatibility

To minimize breaking changes:

  • Keep current behavior as default for permissions (check both with OR)
  • Introduce scopes in non-breaking release
  • Make breaking change in next major version

What about other validators?

Non JWT validators should ignore the scopes property in the firewall, or even better, throw an exception if used since they are not compatible. I also think the JwtAuthValidator should throw an exception if someone attempts to use roles in the firewall rules list. Right now they are just ignored.

Either way, my proposal aligns with how other validators handle roles vs permissions:

AuthValidator has separate roles and permissions properties
BasicAuthValidator has separate roles and permissions properties
Only JwtAuthValidator conflates these concepts into a single permissions property and doesn't even use roles. They silently pass (which feels wrong IMO)

What About the "Secured" Annotation?

I'm open to suggestions about how to best handle a situation where a user uses the secured annotation to create a generic Authorization Context rule. Perhaps in this case we would check both the scope and permissions via a new isAuthorizationContext() method.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions