Skip to content

Authorization

📋 Summary

Forge uses two identity providers to secure API access:

  • Corporate (Entra ID) — internal employees and service accounts
  • External (Okta) — policyholders, injured workers, employers, and providers

Both providers are always active. When you expose permissions on your API or call a downstream API, you must configure both.


🏷️ Key Terminology

Concept What It Is Example
Scope A permission requested by a calling application — on behalf of a user, or as itself Client.Read
App Role A permission granted to a user (via Business Roles) or to an application App.Read
Business Role An organizational position that maps to one or more App Roles Claims Adjuster, HR Manager

🏷️ Permission Naming Conventions

Two prefixes, two mechanisms. The prefix tells you exactly what a permission is and where it goes — the same in both providers:

Entra ID: Scopes and App Roles cannot share the same value

In Entra ID, a scope value and an app_role value on the same app registration must never be identical. With this convention Client.* names only ever appear under scopes: and App.* names only ever under app_roles:, so collisions cannot happen.

Prefix Mechanism Entra config section Okta config file Token claim
Client.* Scope — requested by calling applications infra/api/config.ymlscopes: infra/auth/ext/okta-client/scopes.yml scp
App.* Role — granted to users or applications infra/api/config.ymlapp_roles: infra/auth/ext/okta-client/user_groups.yml roles (Entra) / user-groups (Okta)

Platform-managed delegation scopes

user_impersonation (Entra) and user-groups (Okta) are requested automatically by the platform for user-delegated calls — never add them to options.Scopes or define them as custom scopes. Downstream APIs must still grant them to calling apps in their authorized_apps configuration. Experience APIs additionally expose a platform-managed access scope for the OIDC login flow — leave it alone too.

Service-to-service calls

Okta callers request your Client.* scopes directly. Entra callers always request .default and receive the App.* roles you granted them in authorized_apps (in the token's roles claim). For service-to-service access, define a Client.* scope and a matching App.* role, and accept either in @useAuth — see App Permissions.

When writing raw APIM or JWT claim checks

If you write raw policy or claim validation, check the Token claim column above — Client.* is always in scp, while App.* is in roles (Entra) or user-groups (Okta). If you use the platform's TypeSpec @useAuth decorator and generated APIM policies, the platform handles claim-location differences for you.

App.* is granted, never requested

App.* roles are assigned to users (via Business Roles) or to applications (Entra authorized_apps). Never put App.* names in options.Scopes when configuring a Kiota client — options.Scopes only ever contains Client.* names.

Examples:

  • Client.Read, Client.Write — what a calling application may do (on behalf of a user, or as itself)
  • App.Read, App.Admin — what a user with a certain Business Role, or a service granted the role in Entra, may do

See App Permissions for configuration examples.


🏛️ Dual-Identity Architecture

Aspect Corporate (Entra ID) External (Okta)
Users Internal employees Policyholders, injured workers, employers, providers
Client.* scopes defined in infra/api/config.ymlscopes: infra/auth/ext/okta-client/scopes.yml
App.* roles defined in infra/api/config.ymlapp_roles: 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
Platform delegation scope user_impersonation (auto-added) user-groups (auto-added)

🎫 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 requested scope access
  "scp": [
    "it-api-sys-envsvc.Client.Read",
    "it-api-sys-envsvc.user-groups"
  ],
  "auth_time": 1729721521,
  // sub claim determines the user (subject)
  "sub": "shasca@saif.com",
  // user-groups claim determines user role access
  "user-groups": ["it-api-sys-envsvc.App.Read", "it-api-sys-envsvc.App.Write"]
}

This example shows an Okta token, where permission values include the project ID prefix. In TypeSpec, use the logical permission names (Client.Read, App.Read); the platform-generated APIM policies handle provider-specific formatting.


🛡️ 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