Skip to content

App Permissions

Allow other applications to call your API with proper authorization.

Property Value
Goal Authorize upstream applications to call your API
Prerequisites API deployed, upstream app's Project ID
Time Estimate 15-20 minutes
Difficulty Intermediate

📋 Overview

When your application is an API that other apps need to call, you must configure:

  1. Scopes - What permissions your API exposes
  2. Authorized Apps - Which upstream apps can call your API

Configuration must be done in both identity providers.

App Permission Flow

Upstream App → Requests Token → With Scopes → Calls Your API → Validates Token → Grants Access
               (from Entra/Okta) (User.Read)                   (checks authorized apps)

Step 1: Define API Scopes

Define what permissions your API exposes. Choose scope names that clearly indicate the authorization pattern.

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

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

# Scopes - delegated permissions for user context
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:

# Keep in sync with infra/api/config.yml scopes
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

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

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

# Scopes - application permissions for service-to-service
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:

# Keep in sync with infra/api/config.yml scopes
scopes:
  Client.Read: Read access for service-to-service calls
  Client.Write: Write access for service-to-service calls

Choose Clear Scope Names

  • User-delegated: Use User.*, Profile.*, or Account.* when user context is required
  • Service-to-service: Use Client.*, Service.*, or Data.* for background jobs and system integrations
  • Both files must contain the same scope values

Step 2: Authorize Upstream Apps

Specify which upstream applications are allowed to call your API.

File: infra/auth/corp/config.yml

authorized_apps:
  - project_id: it-web-frontend
    app_roles: []  # Application permissions (service-to-service)
    scopes:        # Delegated permissions (on-behalf-of user)
      - user_impersonation  # Required for user-context calls
      - User.Read
  - project_id: it-api-admin-portal
    app_roles:
      - App.Read
      - App.Write
    scopes:
      - user_impersonation
      - User.Read
      - User.Write

File: infra/auth/ext/app/authorized-apps.yml

authorized_apps:
  - project_id: it-web-frontend
    scopes:
      - user-groups  # Required for user-context calls
      - User.Read
  - project_id: it-api-admin-portal
    scopes:
      - user-groups
      - User.Read
      - User.Write

User-Context Scope Required

Use user_impersonation for corporate (Entra) and user-groups for external (Okta) when granting user-context access.


Step 3: Deploy Configuration

Run the authentication pipeline to apply your changes:

# Run auth pipeline in Azure DevOps
azure-pipelines-auth.yml

Deployment Order

  • Your API must be deployed before upstream apps can be authorized
  • Upstream apps must be deployed before they can call your API

Removing Authorized Apps

When removing an authorized app and the scopes it uses, you must follow a specific order. Entra ID requires that all pre-authorized applications are removed before the scopes they reference are deleted. Removing scopes first causes Terraform to fail when it later tries to clean up the pre-authorized app entries.

Safe Removal Order

flowchart TD
    A["1. Remove authorized app from\ninfra/auth/corp/config.yml"] --> B["2. Run azure-pipelines-auth.yml\n(removes pre-authorization)"]
    B --> C["3. Remove scope from\ninfra/api/config.yml"]
    C --> D["4. Run azure-pipelines-api.yml\n(removes scope)"]

Step 1 — Remove the entry from infra/auth/corp/config.yml:

# Before
authorized_apps:
  - project_id: it-api-exp-caller
    app_roles: []
    scopes:
      - read

# After
authorized_apps: []

Step 2 — Run azure-pipelines-auth.yml so the pre-authorization is removed from Entra ID.

Step 3 — Remove the scope from infra/api/config.yml:

# Before
scopes:
  - value: read
    display_name: Read Access
    description: Allows the app to read data on behalf of the signed-in user

# After
scopes: []

Step 4 — Run azure-pipelines-api.yml so the scope is removed from the app registration.

Do Not Remove Scopes First

Removing a scope from infra/api/config.yml and deploying before removing the pre-authorized apps that reference it will cause a Terraform error on the next apply. See Troubleshooting below if you have already done this.


✅ Verification

After deploying, verify the configuration:

  1. Check Entra ID: Verify the upstream app has API permissions granted
  2. Check Okta: Verify the upstream app is listed in authorized clients
  3. Test API Call: Have the upstream app call your API and verify success

🔍 Troubleshooting

Upstream App Can't Call Your API

Check:

  • Your API is deployed and accessible
  • The upstream app is listed in your authorized apps configuration:
  • Corporate: infra/auth/corp/config.ymlauthorized_apps
  • External: infra/auth/ext/app/authorized-apps.ymlauthorized_apps
  • Requested scopes are defined in your API configuration
  • The upstream app has the correct Project ID configured
  • User-context scope is included: user_impersonation (corp) or user-groups (ext)

Configuration Out of Sync

Check:

  • Scopes in infra/api/config.yml match infra/auth/ext/okta-client/scopes.yml
  • Both corp and ext authorized apps are updated
  • Auth pipeline has been run after all configuration changes

Terraform Fails When Removing Pre-Authorized Apps

Error:

Error: Removing pre-authorized application "..." from Application (Application: "...")

unexpected status 400 (400 Bad Request) with error: InvalidValue: Property
api.preAuthorizedApplications.delegatedPermissionIds has a Permission Id
that cannot be found in the AppPermissions sets.

Cause: A scope was removed from infra/api/config.yml and deployed before the pre-authorized apps referencing it were removed. Entra ID now rejects the pre-authorization removal because it references permission IDs that no longer exist on the application.

Fix: Temporarily restore the missing scope, remove the pre-authorized apps, then remove the scope:

  1. Re-add the deleted scope back to infra/api/config.yml and run azure-pipelines-api.yml
  2. Remove the authorized app entry from infra/auth/corp/config.yml and run azure-pipelines-auth.yml
  3. Remove the scope from infra/api/config.yml again and run azure-pipelines-api.yml

Avoid This in Future

Always remove pre-authorized apps before removing the scopes they use. See Removing Authorized Apps for the correct order.


💡 Common Patterns

API-Only Service

For APIs that are only called by other services (no direct user access):

# 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

# infra/auth/ext/app/authorized-apps.yml
authorized_apps:
  - project_id: it-api-caller
    scopes:
      - Client.Read

No user_groups.yml needed if no human users access directly.

Web Application Backend

For APIs that serve a web frontend:

authorized_apps:
  - project_id: it-web-frontend
    app_roles: []
    scopes:
      - user_impersonation
      - User.Read
      - User.Write

📖 Synchronization Checklist

When updating security configuration, ensure both identity providers are updated:

What Changed Update Corp Update Ext
Added new scope infra/api/config.yml infra/auth/ext/okta-client/scopes.yml
Authorized new upstream app infra/auth/corp/config.yml infra/auth/ext/app/authorized-apps.yml