User Permissions¶
Set up Business Roles and map them to application permissions for user access control.
| Property | Value |
|---|---|
| Goal | Configure which users can access your application |
| Prerequisites | Forge API project created, understanding of Business Roles |
| Time Estimate | 15-30 minutes |
| Difficulty | Intermediate |
📋 Overview¶
User permissions control which Business Roles can access your application and what they can do. Configuration must be done in both identity providers to support corporate and external users. Users authenticate through Entra or Okta and receive your App Roles through their Business Roles; see How Business Roles Work for the full flow.
Step 1: Define Application Roles¶
Define the roles available in your application in infra/api/config.yml:
# App Roles - assigned to users/groups and applications
app_roles:
- value: App.Read
display_name: Read Access
description: Allows reading data
- value: App.Write
display_name: Write Access
description: Allows writing data
- value: App.Admin
display_name: Administrator
description: Full administrative access
Entra ID Constraint
App roles and scopes cannot share the same value in Entra ID. Use the permission naming convention: App.* for app roles and Client.* for scopes.
For external (Okta) authentication, also define permissions in infra/auth/ext/okta-client/user_groups.yml:
# Keep in sync with infra/api/config.yml app_roles
app_permissions:
App.Read: Allows reading data
App.Write: Allows writing data
App.Admin: Full administrative access
Keep Files Synchronized
Both files must contain the same role values. Changes to one should be reflected in the other.
Step 2: Map Business Roles to App Roles¶
Configure which Business Roles have access to which roles in both identity providers:
| Provider | File | Key |
|---|---|---|
| Corporate (Entra ID) | infra/auth/corp/config.yml |
business_roles |
| External (Okta) | infra/auth/ext/user/business-role-app-role.yml |
authorized_business_roles |
Each entry names an existing Business Role and lists the App.* roles from Step 1 that it receives. See Using Business Roles in Your App for the YAML for each provider and the required deployment order.
Step 3: Deploy Your API¶
Before running authentication pipelines, your API must be deployed:
- Deploy your API using the standard pipeline (
azure-dotnet-api.yml) - This creates the Entra app registration and Okta client
- Required: API must be deployed to all environments including production before auth pipelines will work
Step 4: Run Authentication Pipeline¶
Apply your security configuration:
Option A: Standalone Auth Pipeline
Run azure-pipelines-auth.yml to deploy auth configuration independently:
- Use when you only need to update auth configuration
- Deploys both corporate (Entra) and external (Okta) auth
- Useful for granting new apps access without redeploying your API
Option B: With API Deployment
Auth configuration also deploys automatically with your API:
- Standard API deployment includes auth configuration
- Use for regular deployments where both code and auth change
✅ Verification¶
After deploying, verify the configuration:
- Check Entra ID: Verify app roles appear in the Enterprise Application
- Check Okta: Verify groups are created for each app role
- Test Access: Log in as a user with a mapped Business Role and verify permissions
My Apps visibility
Enterprise applications created by the identity module are hidden from users' Microsoft 365 My Apps page by default (feature_tags.hide is set on the service principal). This is controlled by the identity module's visible_to_users variable, which defaults to false. Set visible_to_users = true for user-facing web apps where users sign in directly, such as saif-web-service, once it pins a resources/saif registry version that publishes this input. APIs, event subscribers, and other apps consumed programmatically should keep the default and stay hidden.
saif-web-service is exact-pinned to a resources/saif version that predates visible_to_users, so it is not affected by this default either way yet: its enterprise application stays on its current, always-visible behavior until the caller is wired up as described above.
🔍 Troubleshooting¶
Users Can't Access the Application¶
✅ Check:
- Business Role is correctly mapped in the appropriate config file:
- Corporate users:
infra/auth/corp/config.yml→business_roles - External users:
infra/auth/ext/user/business-role-app-role.yml→authorized_business_roles
- Corporate users:
- User has the correct Business Role assigned in the identity system
- Application roles are defined in
infra/api/config.yml - Auth pipeline has been run after configuration changes
"Insufficient Permissions" Errors¶
✅ Check:
- The Business Role has the necessary app roles assigned
- The app roles match what the application code expects
- Both corp and ext configurations are in sync
Configuration Out of Sync¶
✅ Check:
- App roles in
infra/api/config.ymlmatchinfra/auth/ext/okta-client/user_groups.yml - Auth pipeline has been run after all configuration changes
📋 TypeSpec Auth Configuration¶
Your API's TypeSpec definition includes an @useAuth decorator that specifies required authentication. One declaration covers both providers and all access patterns:
Roles<> is the helper that users with a mapped Business Role satisfy. How Authentication Works explains which token claim each helper checks in each provider.
Experience APIs and the access scope
External-facing Experience APIs are generated with Scopes<["access"]> instead of a Client.* scope. The access scope is platform-managed — automatically exposed for the OIDC login flow — so you don't define it in your config files. Treat it like user_impersonation/user-groups: leave it alone, and use Client.* for any custom scopes you add.
Ensure your TypeSpec auth decorator matches your configuration files:
| TypeSpec Reference | Configuration File | Key |
|---|---|---|
Roles<["App.Read"]> |
infra/api/config.yml |
app_roles |
Scopes<["Client.Read"]> |
infra/api/config.yml |
scopes |
Tip
The template generates the correct @useAuth decorator based on your API type. If you add custom roles, update both the TypeSpec file and configuration files.
📚 Related Documentation¶
- Understanding Business Roles - How Business Roles work
- Configure App Permissions - Allow other apps to call your API
- Calling Downstream APIs - Configure Kiota clients with scopes