Skip to content

Role Based Access Control for Consent Portal #77

Closed

Description

@rashmithachamikara

Requirement

Conditionally show pages and actions based on the authenticated user's permissions:

  • Administrators can manage elements and purposes and view all consents.
  • DPOs can view all consents and will later access grievances.
  • End users can view and manage only their own consents.

Backend APIs must enforce the same permissions independently of UI visibility (It already does as at now).

Reference: APIM Portals

APIM maps roles to OAuth scopes. The frontend does not communicate directly with the identity provider. It calls APIM’s backend introspection endpoint, which reconstructs the access token and sends it to the identity provider for introspection (introspect.jsp).

The backend returns the introspection result to the frontend. The frontend extracts the granted scopes and stores them on the current user (AuthManager.js).

UI elements are wrapped with ScopeValidation, which compares the user’s scopes with those required by the API operation. For example, the Delete API button is conditionally displayed using this component (DeleteApiButton.jsx).

The APIs independently enforce the same scopes. UI visibility is therefore a usability feature rather than the security boundary.

Suggested approach

Use the existing API scopes as the common permission model for frontend visibility and backend authorization. Avoid checking role names such as Admin or DPO directly in the frontend.

The frontend should call the backend /me endpoint and must not interact directly with the identity provider. The backend should:

  1. Reconstruct the access token from its split-token storage.
  2. Validate the JWT signature using the identity provider’s JWKS.
  3. Validate claims such as issuer, audience, expiry, subject, and organization.
  4. Extract the granted scopes from the validated token.
  5. Return a minimal current-user response:
{
  "authenticated": true,
  "userId": "user-123",
  "organizationId": "org-1",
  "scopes": [
    "portal:consents:read:any",
    "portal:elements:read"
  ]
}

The frontend should load /me during application initialization and provide reusable authorization utilities for:

  • Hiding inaccessible navigation items.
  • Protecting routes from direct navigation.
  • Hiding or disabling create, edit, delete, and revoke actions.
  • Checking whether the user has any or all required scopes.

Use the following role-to-scope mappings:

  • Administrator: consent read/write-any and element/purpose read/write scopes.
  • DPO: consent read-any and future grievance scopes.
  • End user: consent read/write-self scopes.

Every backend endpoint must continue enforcing its required scope using the validated principal. Frontend checks are only for presentation and navigation and are not the security boundary.

Reuse API scopes when a page represents access to the corresponding API capability. Add a new scope only for a genuinely distinct permission, such as grievance access, rather than introducing separate UI-only scopes.

Activity

  1. rashmithachamikara commented on Jul 31, 2026

    @rashmithachamikara
    ContributorAuthor
  2. rashmithachamikara commented on Jul 31, 2026

    @rashmithachamikara
    ContributorAuthor

    Example from reference

    APIM uses a reusable React wrapper called ScopeValidation to conditionally render UI elements.

    Example: showing the “Delete API” button

    1. After login, APIM introspects the access token, splits the returned scope string, and stores it on the frontend user:
    const scopes = data.scope.split(' ');
    user.scopes = scopes;

    Source: AuthManager.js lines 92–100

    1. The Delete button is wrapped in ScopeValidation, identified by its API resource path and HTTP method:
    <ScopeValidation
        resourceMethod={resourceMethod.DELETE}
        resourcePath={resourcePath.SINGLE_API}
    >
        <IconButton>
            <DeleteIcon />
        </IconButton>
    </ScopeValidation>

    Source: DeleteApiButton.jsx lines 292–310

    1. ScopeValidation obtains:
    • The user’s granted scopes from AuthManager.
    • The scopes required for that API operation from APIClient.

    It returns true if the user has at least one accepted scope:

    const userScopes = AuthManager.getUser().scopes;
    const validScope =
        APIClient.getScopeForResource(currentResourcePath, currentResourceMethod);
    
    return validScope.then((scopes) => {
        for (const scope of scopes) {
            if (userScopes.includes(scope)) {
                return true;
            }
        }
        return false;
    });

    Source: ScopeValidation.jsx lines 88–98

    1. APIClient.getScopeForResource() reads the accepted scopes from the Publisher OpenAPI definition:
    resolved.spec.paths[resourcePath]
        [resourceMethod]
        .security[0]
        .OAuth2Security

    Source: APIClient.js lines 88–104

    For DELETE /apis/{apiId}, the API declares these accepted scopes:

    @AuthorizationScope(scope = "apim:api_delete", ...)
    @AuthorizationScope(scope = "apim:api_manage", ...)
    @AuthorizationScope(scope = "apim:api_import_export", ...)

    Source: ApisApi.java lines 448–457

    1. Finally, ScopeValidation.render() either returns the wrapped button or null:
    if (haveScope) {
        return children || null;
    }
    return null;

    Source: ScopeValidation.jsx lines 115–134

    Therefore:

    User scopes
        ↓
    ScopeValidation
        ↓
    Accepted scopes for HTTP method + resource path
        ↓
    Any matching scope?
        ├─ Yes → render UI element
        └─ No  → return null
    

    A notable design choice is that the component does not hardcode apim:api_delete. It identifies the operation as DELETE /apis/{apiId} and derives the accepted scopes from the API specification. The backend still enforces those scopes when the delete request is made; hiding the button is only the frontend presentation layer.

  3. rashmithachamikara commented on Aug 4, 2026

    @rashmithachamikara
    ContributorAuthor

    Implemented in #81

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions