Skip to content

Authorization

📋 Summary

User access to APIs is achieved using the OAuth 2.0 concept of delegated authorization. This is where an application itself is authorized to access a resource on behalf of the user. In order for a request to be authorized, both of the following conditions must be true:

  • The requesting application has been granted the ability to access the requested resource on behalf of users
  • The user whom the request is on behalf of must have been granted access to the requested resource

🏷️ Key Terminology

Understanding the difference between Scopes, App Roles, and Business Roles is essential for configuring authorization correctly.

Scopes vs. App Roles vs. Business Roles

Concept What It Is Who Gets It Where Defined Example
Scope Permission for app-to-app communication Applications (not users) infra/api/config.yml (Entra)
infra/auth/ext/okta-client/scopes.yml (Okta)
User.Read, User.Write
App Role Permission for user access within an app Users via Business Roles infra/api/config.yml (Entra)
infra/auth/ext/okta-client/user_groups.yml (Okta)
App.Read, App.Write
Business Role Organizational position that grants App Roles Users based on job function Business Roles Repository Claims Adjuster, HR Manager

When to Use Each

flowchart TD
    A[Need to authorize access?] --> B{Who is accessing?}
    B -->|Another application| C[Use Scopes]
    B -->|A user| D{How does the user get access?}
    D -->|Based on job function| E[Use Business Roles → App Roles]
    D -->|Directly granted| F[Use App Roles directly]

    C --> G["Define in infra/api/config.yml (scopes)"]
    E --> H["Map Business Role to App Roles in auth config"]
    F --> I["Assign App Role directly to user/group"]

Scope Naming Conventions

Choose scope names that clearly indicate the authorization pattern:

Use when applications call your API on behalf of a user (token exchange flow).

Recommended naming: User.*, Profile.*, Account.*

Examples: - User.Read - Read data on behalf of the user - User.Write - Write data on behalf of the user - Profile.Manage - Manage user profile

When to use: - Frontend applications calling your API - APIs calling downstream APIs with user context - Any scenario where user identity is required

Use for direct service-to-service calls with no user context (client credentials flow).

Recommended naming: Client.*, Service.*, Data.*

Examples: - Client.Read - Read access for service-to-service calls - Client.Write - Write access for service-to-service calls - Data.Sync - Data synchronization operations

When to use: - Background jobs or scheduled tasks - System-to-system integrations - APIs that only serve other services (no direct user access)

Clear Naming Prevents Confusion

Using User.* for service-to-service scopes creates confusion about whether user context is involved. Choose names that accurately reflect your authentication pattern.

Practical Examples

Scenario 1: Frontend app calling your API

  • The frontend app needs Scopes (User.Read) to call your API on behalf of users
  • Configure in authorized_apps with the scopes it needs

Scenario 2: Claims Adjuster accessing your app

  • The user has the Business Role Claims Adjuster
  • Your app maps Claims AdjusterApp Roles [App.Read, App.Write]
  • User can now read and write data

Scenario 3: Service-to-service call (no user context)

  • Service A calls Service B directly (not on behalf of a user)
  • Service A needs App Roles (not Scopes) assigned directly
  • Configure in authorized_apps with app_roles

🏛️ Dual-Identity Architecture

Forge uses two identity providers to support both internal and external users:

Aspect Corporate (Entra ID) External (Okta)
Users Internal employees Policyholders, injured workers, employers, providers
Scopes defined in infra/api/config.yml infra/auth/ext/okta-client/scopes.yml
App Roles defined in infra/api/config.yml infra/auth/ext/okta-client/user_groups.yml
Business Role mapping infra/auth/corp/config.yml infra/auth/ext/user/business-role-app-role.yml
User-context scope user_impersonation user-groups

🔑 Scopes - Application Authorization

When an API is deployed, the deploy creates an authorization server in the identity provider, which is used by the API to validate authorization on incoming requests. With the authorization server, items called Scopes are also created. These are declared by the application and used to secure access to resources for other applications.

Where to define scopes:

For applications calling your API on behalf of users.

Corporate (Entra ID) - infra/api/config.yml:

scopes:
  - value: User.Read
    display_name: Read access (Delegated)
    description: Allows the app to read data on behalf of the user
  - value: User.Write
    display_name: Write access (Delegated)
    description: Allows the app to write data on behalf of the user

External (Okta) - infra/auth/ext/okta-client/scopes.yml:

scopes:
  User.Read: Allows apps to read data on behalf of the user
  User.Write: Allows apps to write data on behalf of the user

For direct service-to-service calls with no user context.

Corporate (Entra ID) - infra/api/config.yml:

scopes:
  - value: Client.Read
    display_name: Read access (Service-to-Service)
    description: Allows service-to-service read access
  - value: Client.Write
    display_name: Write access (Service-to-Service)
    description: Allows service-to-service write access

External (Okta) - infra/auth/ext/okta-client/scopes.yml:

scopes:
  Client.Read: Read access for service-to-service calls
  Client.Write: Write access for service-to-service calls

👥 App Roles - User Authorization

App Roles (also called User Groups in Okta) define what actions users can perform in your application. Users receive App Roles through Business Role assignments.

Where to define app roles:

File: infra/api/config.yml

app_roles:
  - value: App.Read
    display_name: Read Access
    description: Allows reading data
  - value: App.Write
    display_name: Write Access
    description: Allows writing data

File: infra/auth/ext/okta-client/user_groups.yml

app_permissions:
  App.Read: Allows reading data
  App.Write: Allows writing data

Entra ID Naming Constraint

In Entra ID, App Roles and Scopes cannot share the same value. Use a naming convention like App.Read for app roles and User.Read for scopes to avoid deployment errors.


🎫 Proof of Identity and Authorization

When accessing a resource, identity and authorization are validated via a JWT that was digitally signed by the authorization server that minted it. This is a JSON payload that contains claims that contain the identity and access authorization.

Sample JWT:

{
  "ver": 1,
  "jti": "AT.Bdk6ykeyEsSGj9sFNvZC7_9LzH7-ypGpK34a2iCZwsQ",
  "iss": "https://saif-oie.oktapreview.com/oauth2/aushpkatj89kOhK6Y1d7",
  "aud": "api://it-api-sys-envsvc",
  "iat": 1729721521,
  "exp": 1729725121,
  "cid": "0oafxgi1h1dCu0ffJ1d7",
  "uid": "00uci6hkxxR6Ms7lk1d7",
  // scp claim determines requesting application access
  "scp": [
    "it-api-sys-envsvc.read",
    "it-api-sys-envsvc.write",
    "it-api-sys-envsvc.user-groups"
  ],
  "auth_time": 1729721521,
  // sub claim determines the user (subject)
  "sub": "shasca@saif.com",
  // user groups claim determines user access
  "user-groups": ["it-api-sys-envsvc.read", "it-api-sys-envsvc.write"]
}

🛡️ Where the Authorization Occurs

Authorization is performed by Azure API Management. When an API is deployed, policies are created on either the API, or individual endpoints specifying values in the scp and user-groups claims of the token required for authorization to occur.

API Policy Types

The api_policy_type setting in infra/api/vars.yml controls how APIM authenticates incoming requests:

Policy Type Authentication Method Use Case
standard JWT validation (Okta + Entra) Default — user-delegated and service-to-service flows with OAuth tokens
filevine JWT validation (Filevine IdP) Filevine webhook integration with dedicated JWT issuer
subscription_key APIM subscription key Legacy server-to-server access without OAuth — see Subscription Key Authentication

Subscription Key Auth

The subscription_key policy type is only available for Experience APIs (is_experience_api = true). It bypasses JWT validation entirely — APIM validates the subscription key natively and sets x-forward-tenant-origin: corp for all requests.


✅ True Authorization

For true authorization, both the requesting app and user the request is on behalf of must be authorized to access the resource. If either does not meet the policy requirements, then access is forbidden by API Management.

True Authorization Venn Diagram