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_appswith the scopes it needs
Scenario 2: Claims Adjuster accessing your app
- The user has the Business Role
Claims Adjuster - Your app maps
Claims Adjuster→ App 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_appswithapp_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:
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:
👥 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
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.

📚 Related Documentation¶
- Security Configuration Guide - Step-by-step instructions for configuring authentication and authorization
- Subscription Key Authentication - Configure APIM subscription key auth for legacy apps
- Business Roles - How Business Roles work and configuration examples
- User Permissions - Map Business Roles to App Roles
- App Permissions - Configure scopes and authorize upstream apps
- Calling Downstream APIs - Configure Kiota clients with proper scopes
- Environments - Environment configurations and deployment flow