# 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:

1. **Define permissions** — declare what your API exposes
2. **Authorize callers** — grant specific apps access to those permissions
3. **Enforce permissions** — declare `@useAuth` in TypeSpec so APIM enforces them

Define your API's permissions in **both** identity providers. Authorize each upstream caller in every provider it uses.

---

## Step 1: Define Permissions

Use the [permission naming convention](../../../reference/authorization.md#permission-naming-conventions) to choose names, then put them in the right config section for each provider.

| Prefix | Mechanism | Entra — `infra/api/config.yml` | Okta |
| ------ | --------- | ------------------------------ | ---- |
| `Client.*` | Scope — requested by callers | `scopes:` | `infra/auth/ext/okta-client/scopes.yml` |
| `App.*` | Role — granted to users or apps | `app_roles:` | `infra/auth/ext/okta-client/user_groups.yml` |

=== "Scopes (Client.*)"

    What calling applications request — on behalf of a user, or as themselves.

    **`infra/api/config.yml`:**

    ```yaml
    scopes:
      - value: Client.Read
        display_name: Read access
        description: Allows the calling app to read data
      - value: Client.Write
        display_name: Write access
        description: Allows the calling app to write data
    ```

    **`infra/auth/ext/okta-client/scopes.yml`:**

    ```yaml
    scopes:
      Client.Read: Allows the calling app to read data
      Client.Write: Allows the calling app to write data
    ```

    !!! tip "Keep both files in sync"
        The same `Client.*` values must appear in both files.

=== "Roles (App.*)"

    What is **granted** — to users via Business Roles, or to applications for Entra service-to-service calls.

    **`infra/api/config.yml`:**

    ```yaml
    app_roles:
      - value: App.Read
        display_name: Read access
        description: Allows reading data
      - value: App.Write
        display_name: Write access
        description: Allows writing data
    ```

    **`infra/auth/ext/okta-client/user_groups.yml`:**

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

    See [User Permissions](user-permissions.md) to map Business Roles to these app roles.

!!! note "Service-to-service needs both"
    Okta callers request your `Client.*` scopes directly. Entra callers always request `.default` and receive the `App.*` roles you grant them in Step 2. To expose a permission for service-to-service access, define the `Client.*` scope **and** a matching `App.*` role (for example `Client.Read` + `App.Read`), then accept either in Step 3.

---

## Step 2: Authorize Upstream Apps

Specify which upstream applications are allowed to call your API. Grant them the permissions they need.

!!! warning "Configure each provider the caller uses"
    List the upstream app in the config for every provider it authenticates through — for most APIs that means both. At runtime each request uses exactly one provider (selected by tenant origin), so a missing entry surfaces only when a caller arrives via that tenant.

=== "Corporate (Entra ID)"

    **`infra/auth/corp/config.yml`:**

    ```yaml
    authorized_apps:
      # User-delegated: frontend calls API on behalf of users
      - project_id: it-web-frontend
        scopes:
          - user_impersonation  # Required for user-context calls (platform-managed)
          - Client.Read
        app_roles: []

      # Service-to-service: Entra callers receive App.* roles (requested via .default)
      - project_id: it-api-worker
        scopes: []
        app_roles:
          - App.Read
    ```

=== "External (Okta)"

    **`infra/auth/ext/app/authorized-apps.yml`:**

    ```yaml
    authorized_apps:
      # User-delegated: frontend calls API on behalf of users
      - project_id: it-web-frontend
        scopes:
          - user-groups         # Required for user-context calls (platform-managed)
          - Client.Read

      # Service-to-service: Okta callers request Client.* scopes directly
      - project_id: it-api-worker
        scopes:
          - Client.Read
    ```

---

## Step 3: Enforce Permissions

Declare `@useAuth` in your TypeSpec so APIM generates the correct enforcement policies. See the [TypeSpec Authorization](../../development/typespec.md) guide for the full syntax.

```typespec
@useAuth(Scopes<["Client.Read"]> | Roles<["App.Read"]>)
```

!!! tip "One expression covers every caller"
    `Scopes<["Client.Read"]>` matches the `scp` claim — delegated callers and Okta service-to-service. `Roles<["App.Read"]>` matches the `roles`/`user-groups` claims — users with Business Roles and Entra service-to-service callers. Together they cover all access paths with one declaration.

---

## Step 4: Deploy

Which pipeline you run depends on what you changed:

| What you changed | Pipeline |
| ---------------- | -------- |
| Permission definitions (`infra/api/config.yml`) or TypeSpec `@useAuth` (Steps 1, 3) | `azure-pipelines-api.yml` |
| Authorized apps only (Step 2) | `azure-pipelines-auth.yml` (faster) — also runs as part of the API pipeline |

!!! note "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, follow this order. Entra ID requires that all pre-authorized applications are removed before the scopes they reference are deleted.

```mermaid
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)"]
```

!!! danger "Do not remove scopes first"
    Removing a scope from `infra/api/config.yml` before removing the pre-authorized apps that reference it will cause a Terraform error. See [Troubleshooting](#troubleshooting) 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 authorized apps for the provider it authenticates through:
  - Corporate: `infra/auth/corp/config.yml` → `authorized_apps`
  - External: `infra/auth/ext/app/authorized-apps.yml` → `authorized_apps`
- Requested permissions are defined in your API configuration
- The upstream app has the correct Project ID configured

### 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.

**Fix:**

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`

---

## 📚 Related Documentation

- [Permission Naming Conventions](../../../reference/authorization.md#permission-naming-conventions) — naming table and rules
- [User Permissions](user-permissions.md) — map Business Roles to App Roles
- [Business Roles](business-roles.md) — how Business Roles work
- [Calling Downstream APIs](../../development/calling-apis.md) — configure your app to call other APIs
