references/api-permissions.md
# API Permissions Guide
This document explains how to configure and manage API permissions for your Microsoft Entra app registration.
## Permission Types
### Delegated Permissions (User Context)
**What:** Application acts on behalf of a signed-in user
**When to use:**
- User is present and can consent
- App needs to access resources as the user
- Interactive authentication flows
**Examples:**
- Read user's email
- Update user's calendar
- Access user's OneDrive files
**Scope format:** User must consent (or admin pre-consents)
### Application Permissions (App Context)
**What:** Application acts with its own identity (no user)
**When to use:**
- Background services, daemons
- Scheduled jobs
- API-to-API calls without user
**Examples:**
- Read all users in organization
- Send mail as any user
- Access all SharePoint sites
**Requirement:** Always requires admin consent
## Permission Scopes
### Understanding Scopes
**Scope:** A string that defines what access is granted
**Format:**
```
{resource}/{permission_name}
Examples:
https://graph.microsoft.com/User.Read
https://graph.microsoft.com/Mail.Send
api://myapi-id/access_as_user
```
### .default Scope
Special scope that includes all configured permissions:
```
https://graph.microsoft.com/.default
api://your-api-id/.default
```
**When to use:**
- Client credentials flow (always)
- Want all pre-configured permissions
- Migrating from v1.0 endpoint
## Microsoft Graph Permissions
### Common Delegated Permissions
| Permission | What it allows | Admin Consent Required |
|------------|---------------|----------------------|
| `User.Read` | Read signed-in user's profile | No |
| `User.ReadWrite` | Read and update user profile | No |
| `User.ReadBasic.All` | Read basic info of all users | No |
| `User.Read.All` | Read all users' full profiles | Yes |
| `Mail.Read` | Read user's mail | No |
| `Mail.ReadWrite` | Read and write user's mail | No |
| `Mail.Send` | Send mail as user | No |
| `Calendars.Read` | Read user's calendars | No |
| `Calendars.ReadWrite` | Read and write calendars | No |
| `Files.Read.All` | Read all files user can access | No |
| `Sites.Read.All` | Read items in all site collections | Yes |
| `Directory.Read.All` | Read directory data | Yes |
| `Directory.ReadWrite.All` | Read and write directory data | Yes |
### Common Application Permissions
| Permission | What it allows | Admin Consent Required |
|------------|---------------|----------------------|
| `User.Read.All` | Read all users' full profiles | Yes (Always) |
| `User.ReadWrite.All` | Read and write all users' profiles | Yes (Always) |
| `Mail.Read` | Read mail in all mailboxes | Yes (Always) |
| `Mail.Send` | Send mail as any user | Yes (Always) |
| `Calendars.Read` | Read calendars in all mailboxes | Yes (Always) |
| `Directory.Read.All` | Read directory data | Yes (Always) |
| `Directory.ReadWrite.All` | Read and write directory data | Yes (Always) |
| `Group.ReadWrite.All` | Read and write all groups | Yes (Always) |
## Adding Permissions
### Azure Portal Method
1. Navigate to your app registration
2. Click **"API permissions"** in left menu
3. Click **"+ Add a permission"**
4. Choose API source:
- **Microsoft APIs** (Graph, Office 365, etc.)
- **APIs my organization uses** (custom APIs)
- **My APIs** (your own APIs)
5. Select permission type:
- **Delegated permissions** (user context)
- **Application permissions** (app context)
6. Search and select permissions
7. Click **"Add permissions"**
See [cli-commands.md](cli-commands.md) for az cli commands to add API permissions programmatically.
## Finding Permission IDs
### Method 1: Azure Portal
1. Go to Microsoft Entra ID → Enterprise applications
2. Search for "Microsoft Graph"
3. Click on it → Permissions
4. Browse available permissions and copy IDs
### Method 2: Microsoft Graph Explorer
1. Visit https://developer.microsoft.com/graph/graph-explorer
2. Click "Modify permissions"
3. Browse and view permission details
### Method 3: Microsoft Documentation
Visit: https://learn.microsoft.com/en-us/graph/permissions-reference
### Method 4: Azure CLI Query
```bash
# List all Graph permissions (warning: long output)
az ad sp list --filter "appId eq '00000003-0000-0000-c000-000000000000'" \
--query "[0].{delegated:oauth2PermissionScopes,application:appRoles}" -o json
```
## Granting Admin Consent
### When Admin Consent is Required
**Always required for:**
- All application permissions
- High-privilege delegated permissions
- When organization disables user consent
**Examples requiring admin consent:**
- `User.Read.All` (read all users)
- `Directory.Read.All` (read directory)
- `Mail.Read` (application permission)
- `Sites.Read.All` (read all SharePoint sites)
### How to Grant Admin Consent
**Portal Method:**
1. Go to API permissions
2. Click **"Grant admin consent for [Your Org]"**
3. Confirm the action
4. Check for green checkmarks next to permissions
**CLI Method:**
```bash
az ad app permission admin-consent --id $APP_ID
```
### Verifying Consent Status
**Portal:** Look for green checkmarks in "Status" column
**CLI:**
```bash
az ad app permission list --id $APP_ID
```
Look for `consentType: "AllPrincipals"` (admin consented)
## Custom API Permissions
### Exposing Your API
If you're building an API that other apps will call:
1. In your API's app registration, go to **"Expose an API"**
2. Set **Application ID URI**: `api://your-api-id`
3. Click **"+ Add a scope"**
4. Configure scope:
- **Scope name:** `access_as_user`
- **Who can consent:** Admins and users
- **Display name:** "Access MyAPI as user"
- **Description:** Clear description of what this allows
5. Click **"Add scope"**
## Effective Permissions
### User + App Permissions
**Delegated permissions:** Intersection of user's permissions and app's permissions
Example:
- User can: Read all users
- App granted: User.Read.All
- **Effective:** Read all users ✅
- User can: Only read their own profile
- App granted: User.Read.All
- **Effective:** Only read own profile (limited by user's rights)
**Application permissions:** Only app's permissions matter (no user context)
## Troubleshooting Permissions
### "Insufficient privileges" Error
**Causes:**
- Permission not added to app registration
- Admin consent not granted
- User lacks permission in directory
- Accessing resource outside permission scope
**Solutions:**
1. Check API permissions in portal
2. Grant admin consent if needed
3. Verify user has access to resource
4. Use correct permission scope
### "Consent required" Error
**Causes:**
- User hasn't consented to permissions
- Admin consent required but not granted
- Token obtained before permission added
**Solutions:**
1. Request user consent (interactive flow)
2. Admin grants consent (portal or CLI)
3. Acquire new token after adding permissions
### Permission Appears Granted but Doesn't Work
**Possible issues:**
- Using old cached token (get new one)
- Permission is delegated but user lacks rights
- API requires additional configuration
- Permission deprecated (use new one)
**Debug steps:**
1. Decode access token: https://jwt.ms
2. Check `scp` claim (delegated) or `roles` claim (application)
3. Verify permission is present in token
4. Check if permission is correct type (delegated vs application)
## Permission Best Practices
### Development
✅ **Do:**
- Start with minimal permissions
- Add incrementally as features require
- Test with non-admin accounts
- Document why each permission is needed
❌ **Don't:**
- Request all permissions "just in case"
- Use admin account for testing only
- Forget to grant admin consent for app permissions
### Production
✅ **Do:**
- Review permissions quarterly
- Remove unused permissions
- Use least privilege principle
- Monitor permission usage
- Document all permissions in README
❌ **Don't:**
- Grant excessive permissions for convenience
- Use application permissions when delegated would work
- Forget to rotate admin consent approvals
### Security
✅ **Do:**
- Prefer delegated over application permissions
- Implement proper scope validation
- Log permission usage
- Handle consent errors gracefully
❌ **Don't:**
- Hardcode permission scopes in multiple places
- Skip token validation
- Ignore scope mismatches
- Cache permissions indefinitely
## Reference Tables
### Microsoft Graph Permission IDs
**Delegated Permissions:**
```
User.Read : e1fe6dd8-ba31-4d61-89e7-88639da4683d
User.ReadWrite : b4e74841-8e56-480b-be8b-910348b18b4c
User.ReadBasic.All : b340eb25-3456-403f-be2f-af7a0d370277
Mail.Read : 570282fd-fa5c-430d-a7fd-fc8dc98a9dca
Mail.ReadWrite : 024d486e-b451-40bb-833d-3e66d98c5c73
Mail.Send : e383f46e-2787-4529-855e-0e479a3ffac0
Calendars.Read : 465a38f9-76ea-45b9-9f34-9e8b0d4b0b42
Calendars.ReadWrite : 1ec239c2-d7c9-4623-a91a-a9775856bb36
Files.Read.All : df85f4d6-205c-4ac5-a5ea-6bf408dba283
```
**Application Permissions:**
```
User.Read.All : df021288-bdef-4463-88db-98f22de89214
User.ReadWrite.All : 741f803b-c850-494e-b5df-cde7c675a1ca
Mail.Read : 810c84a8-4a9e-49e6-bf7d-12d183f40d01
Mail.Send : b633e1c5-b582-4048-a93e-9f11b44c7e96
Directory.Read.All : 7ab1d382-f21e-4acd-a863-ba3e13f7da61
Directory.ReadWrite.All : 19dbc75e-c2e2-444c-a770-ec69d8559fc7
```
**Note:** Permission IDs may change. Always verify against the official [Microsoft Graph Permissions Reference](https://learn.microsoft.com/en-us/graph/permissions-reference) for the most current values.
## Additional Resources
- [Microsoft Graph Permissions Reference](https://learn.microsoft.com/en-us/graph/permissions-reference)
- [Permission Types](https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview)
- [Admin Consent Workflow](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow)
- [Consent Framework](https://learn.microsoft.com/en-us/entra/identity-platform/consent-framework)
references/auth-best-practices.md
# Azure Authentication Best Practices
> Source: [Microsoft — Passwordless connections for Azure services](https://learn.microsoft.com/azure/developer/intro/passwordless-overview) and [Azure Identity client libraries](https://learn.microsoft.com/dotnet/azure/sdk/authentication/).
## Golden Rule
Use **managed identities** and **Azure RBAC** in production. Reserve `DefaultAzureCredential` for **local development only**.
## Authentication by Environment
| Environment | Recommended Credential | Why |
|---|---|---|
| **Production (Azure-hosted)** | `ManagedIdentityCredential` (system- or user-assigned) | No secrets to manage; auto-rotated by Azure |
| **Production (on-premises)** | `ClientCertificateCredential` or `WorkloadIdentityCredential` | Deterministic; no fallback chain overhead |
| **CI/CD pipelines** | `AzurePipelinesCredential` / `WorkloadIdentityCredential` | Scoped to pipeline identity |
| **Local development** | `DefaultAzureCredential` | Chains CLI, PowerShell, and VS Code credentials for convenience |
## Why Not `DefaultAzureCredential` in Production?
1. **Unpredictable fallback chain** — walks through multiple credential types, adding latency and making failures harder to diagnose.
2. **Broad surface area** — checks environment variables, CLI tokens, and other sources that should not exist in production.
3. **Non-deterministic** — which credential actually authenticates depends on the environment, making behavior inconsistent across deployments.
4. **Performance** — each failed credential attempt adds network round-trips before falling back to the next.
## Production Patterns
### .NET
```csharp
using Azure.Identity;
var credential = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"
? new DefaultAzureCredential() // local dev — uses CLI/VS credentials
: new ManagedIdentityCredential(); // production — deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredential("<client-id>")
```
### TypeScript / JavaScript
```typescript
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
const credential = process.env.NODE_ENV === "development"
? new DefaultAzureCredential() // local dev — uses CLI/VS credentials
: new ManagedIdentityCredential(); // production — deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredential("<client-id>")
```
### Python
```python
import os
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
credential = (
DefaultAzureCredential() # local dev — uses CLI/VS credentials
if os.getenv("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"
else ManagedIdentityCredential() # production — deterministic, no fallback chain
)
# For user-assigned identity: ManagedIdentityCredential(client_id="<client-id>")
```
### Java
```java
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.identity.ManagedIdentityCredentialBuilder;
var credential = "Development".equals(System.getenv("AZURE_FUNCTIONS_ENVIRONMENT"))
? new DefaultAzureCredentialBuilder().build() // local dev — uses CLI/VS credentials
: new ManagedIdentityCredentialBuilder().build(); // production — deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredentialBuilder().clientId("<client-id>").build()
```
## Local Development Setup
`DefaultAzureCredential` is ideal for local dev because it automatically picks up credentials from developer tools:
1. **Azure CLI** — `az login`
2. **Azure Developer CLI** — `azd auth login`
3. **Azure PowerShell** — `Connect-AzAccount`
4. **Visual Studio / VS Code** — sign in via Azure extension
```typescript
import { DefaultAzureCredential } from "@azure/identity";
// Local development only — uses CLI/PowerShell/VS Code credentials
const credential = new DefaultAzureCredential();
```
## Environment-Aware Pattern
Detect the runtime environment and select the appropriate credential. The key principle: use `DefaultAzureCredential` only when running locally, and a specific credential in production.
> **Tip:** Azure Functions sets `AZURE_FUNCTIONS_ENVIRONMENT` to `"Development"` when running locally. For App Service or containers, use any environment variable you control (e.g. `NODE_ENV`, `ASPNETCORE_ENVIRONMENT`).
```typescript
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
function getCredential() {
if (process.env.NODE_ENV === "development") {
return new DefaultAzureCredential(); // picks up az login / VS Code creds
}
return process.env.AZURE_CLIENT_ID
? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID) // user-assigned
: new ManagedIdentityCredential(); // system-assigned
}
```
## Security Checklist
- [ ] Use managed identity for all Azure-hosted apps
- [ ] Never hardcode credentials, connection strings, or keys
- [ ] Apply least-privilege RBAC roles at the narrowest scope
- [ ] Use `ManagedIdentityCredential` (not `DefaultAzureCredential`) in production
- [ ] Store any required secrets in Azure Key Vault
- [ ] Rotate secrets and certificates on a schedule
- [ ] Enable Microsoft Defender for Cloud on production resources
## Further Reading
- [Passwordless connections overview](https://learn.microsoft.com/azure/developer/intro/passwordless-overview)
- [Managed identities overview](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview)
- [Azure RBAC overview](https://learn.microsoft.com/azure/role-based-access-control/overview)
- [.NET authentication guide](https://learn.microsoft.com/dotnet/azure/sdk/authentication/)
- [Python identity library](https://learn.microsoft.com/python/api/overview/azure/identity-readme)
- [JavaScript identity library](https://learn.microsoft.com/javascript/api/overview/azure/identity-readme)
- [Java identity library](https://learn.microsoft.com/java/api/overview/azure/identity-readme)
references/BICEP-EXAMPLE.bicep
// Bicep template for Microsoft Entra App Registration
// Requires: Bicep v0.21.1+ with Microsoft Graph extension enabled
extension 'br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:1.0.0'
@description('Display name for the application')
param appDisplayName string = 'MyEntraApp'
@description('Sign-in audience for the application')
@allowed([
'AzureADMyOrg'
'AzureADMultipleOrgs'
'AzureADandPersonalMicrosoftAccount'
'PersonalMicrosoftAccount'
])
param signInAudience string = 'AzureADMyOrg'
@description('Redirect URIs for web application')
param webRedirectUris array = [
'https://localhost:5001/signin-oidc'
'https://myapp.azurewebsites.net/signin-oidc'
]
@description('Redirect URIs for single-page application')
param spaRedirectUris array = [
'http://localhost:3000'
'https://myapp.azurewebsites.net'
]
@description('Tags for the application')
param tags array = [
'Production'
'WebApp'
]
// App Registration
resource appRegistration 'Microsoft.Graph/applications@v1.0' = {
displayName: appDisplayName
uniqueName: toLower(replace(appDisplayName, ' ', '-'))
signInAudience: signInAudience
tags: tags
// Application identification
identifierUris: [
'api://${appDisplayName}'
]
// Web application settings
web: {
redirectUris: webRedirectUris
implicitGrantSettings: {
enableIdTokenIssuance: true
enableAccessTokenIssuance: false
}
homePageUrl: 'https://myapp.azurewebsites.net'
logoutUrl: 'https://myapp.azurewebsites.net/signout-oidc'
}
// Single-page application settings
spa: {
redirectUris: spaRedirectUris
}
// Public client (mobile/desktop) settings
publicClient: {
redirectUris: [
'http://localhost'
'myapp://auth'
'https://login.microsoftonline.com/common/oauth2/nativeclient'
]
}
// API definition (expose an API)
api: {
// Version of the access token affects the values present in the token claims
requestedAccessTokenVersion: 2
oauth2PermissionScopes: [
{
id: '00000000-0000-0000-0000-000000000001'
adminConsentDisplayName: 'Read user data'
adminConsentDescription: 'Allows the app to read user data on behalf of the signed-in user'
userConsentDisplayName: 'Read your data'
userConsentDescription: 'Allows the app to read your data'
value: 'User.Read'
type: 'User'
isEnabled: true
}
{
id: '00000000-0000-0000-0000-000000000002'
adminConsentDisplayName: 'Read and write user data'
adminConsentDescription: 'Allows the app to read and write user data on behalf of the signed-in user'
userConsentDisplayName: 'Read and write your data'
userConsentDescription: 'Allows the app to read and write your data'
value: 'User.ReadWrite'
type: 'User'
isEnabled: true
}
]
}
// App roles for authorization
appRoles: [
{
id: '00000000-0000-0000-0000-000000000010'
displayName: 'Admin'
description: 'Administrators can manage all aspects of the app'
value: 'Admin'
allowedMemberTypes: ['User', 'Application']
isEnabled: true
}
{
id: '00000000-0000-0000-0000-000000000011'
displayName: 'Reader'
description: 'Readers can view data but not modify'
value: 'Reader'
allowedMemberTypes: ['User']
isEnabled: true
}
]
// Required API permissions (Microsoft Graph)
requiredResourceAccess: [
{
// Microsoft Graph API
resourceAppId: '00000003-0000-0000-c000-000000000000'
resourceAccess: [
{
// User.Read - Delegated
id: 'e1fe6dd8-ba31-4d61-89e7-88639da4683d'
type: 'Scope'
}
{
// User.ReadBasic.All - Delegated
id: 'b340eb25-3456-403f-be2f-af7a0d370277'
type: 'Scope'
}
{
// Mail.Read - Delegated
id: '570282fd-fa5c-430d-a7fd-fc8dc98a9dca'
type: 'Scope'
}
{
// User.Read.All - Application
id: 'df021288-bdef-4463-88db-98f22de89214'
type: 'Role'
}
]
}
]
// Optional claims configuration
optionalClaims: {
idToken: [
{
name: 'email'
essential: false
}
{
name: 'upn'
essential: false
}
{
name: 'groups'
essential: false
}
]
accessToken: [
{
name: 'email'
essential: false
}
]
}
// Information URLs
info: {
marketingUrl: 'https://myapp.example.com'
privacyStatementUrl: 'https://myapp.example.com/privacy'
supportUrl: 'https://myapp.example.com/support'
termsOfServiceUrl: 'https://myapp.example.com/terms'
}
}
// Service Principal (Enterprise Application)
resource servicePrincipal 'Microsoft.Graph/servicePrincipals@v1.0' = {
appId: appRegistration.appId
displayName: appDisplayName
tags: [
'WindowsAzureActiveDirectoryIntegratedApp'
]
appRoleAssignmentRequired: false
preferredSingleSignOnMode: 'oidc'
}
// Outputs
output applicationId string = appRegistration.appId
output objectId string = appRegistration.id
output servicePrincipalId string = servicePrincipal.id
output identifierUri string = appRegistration.identifierUris[0]
references/cli-commands.md
# Azure CLI Commands for App Registration
This document provides a comprehensive reference for managing Microsoft Entra app registrations using Azure CLI.
## Prerequisites
```bash
# Ensure Azure CLI is installed
az version
# Login to Azure
az login
# Set default subscription (optional)
az account set --subscription "Your Subscription Name"
```
## App Registration Management
### Create App Registration
**Basic app registration:**
```bash
az ad app create --display-name "MyApplication"
```
**Web application with redirect URI:**
```bash
az ad app create \
--display-name "MyWebApp" \
--web-redirect-uris "https://myapp.com/callback" \
--sign-in-audience "AzureADMyOrg"
```
**Single Page Application (SPA):**
```bash
az ad app create \
--display-name "MySpaApp" \
--spa-redirect-uris "http://localhost:3000" \
--sign-in-audience "AzureADMyOrg"
```
**Public client (Desktop/Mobile app):**
```bash
az ad app create \
--display-name "MyDesktopApp" \
--public-client-redirect-uris "http://localhost" \
--sign-in-audience "AzureADMyOrg"
```
**Multi-tenant application:**
```bash
az ad app create \
--display-name "MyMultiTenantApp" \
--web-redirect-uris "https://myapp.com/callback" \
--sign-in-audience "AzureADMultipleOrgs"
```
### Sign-in Audience Options
| Value | Description |
|-------|-------------|
| `AzureADMyOrg` | Single tenant (default) |
| `AzureADMultipleOrgs` | Multi-tenant (any Azure AD) |
| `AzureADandPersonalMicrosoftAccount` | Multi-tenant + personal Microsoft accounts |
| `PersonalMicrosoftAccount` | Personal Microsoft accounts only |
## List and Query Apps
### List all app registrations
```bash
az ad app list --output table
```
### List apps with custom query
```bash
# Filter by display name
az ad app list --display-name "MyApp" --output table
# Get specific fields
az ad app list --query "[].{Name:displayName, AppId:appId}" --output table
```
### Get app details
```bash
# By display name
az ad app show --id $(az ad app list --display-name "MyApp" --query "[0].appId" -o tsv)
# By application ID
az ad app show --id "YOUR_APPLICATION_ID"
```
### Get Application (Client) ID
```bash
APP_ID=$(az ad app list --display-name "MyApp" --query "[0].appId" -o tsv)
echo "Application ID: $APP_ID"
```
### Get Object ID
```bash
OBJECT_ID=$(az ad app list --display-name "MyApp" --query "[0].id" -o tsv)
echo "Object ID: $OBJECT_ID"
```
## Update App Registration
### Add redirect URIs
**Web app:**
```bash
az ad app update --id $APP_ID \
--web-redirect-uris "https://myapp.com/callback" "https://myapp.com/auth"
```
**SPA:**
```bash
az ad app update --id $APP_ID \
--spa-redirect-uris "http://localhost:3000" "http://localhost:5000"
```
**Public client:**
```bash
az ad app update --id $APP_ID \
--public-client-redirect-uris "http://localhost" "myapp://auth"
```
## Client Credentials (Secrets & Certificates)
### Create client secret
```bash
# Create secret with default expiration
az ad app credential reset --id $APP_ID
# Create secret with custom expiration
az ad app credential reset --id $APP_ID --years 1
# Create secret with specific end date
az ad app credential reset --id $APP_ID --end-date "2025-12-31"
```
**Save the output:**
```json
{
"appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"password": "your-secret-value-SAVE-THIS",
"tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
**⚠️ Important:** Resetting Client credential will delete all existing credentials.
**⚠️ Important:** The secret value is only shown once. Store it securely (e.g., Azure Key Vault).
### List client credentials
```bash
# List all credentials (secrets and certificates)
az ad app credential list --id $APP_ID
```
### Delete client secret
```bash
# Get key ID from credential list
az ad app credential list --id $APP_ID --query "[].{KeyId:keyId, Type:type}" -o table
# Delete specific credential
az ad app credential delete --id $APP_ID --key-id "KEY_ID_HERE"
```
### Upload certificate
```bash
# Upload certificate from file
az ad app credential reset --id $APP_ID --cert "@path/to/cert.pem"
```
## API Permissions
### Add API permissions
**Microsoft Graph User.Read:**
```bash
GRAPH_RESOURCE_ID="00000003-0000-0000-c000-000000000000" # Microsoft Graph
USER_READ_ID="e1fe6dd8-ba31-4d61-89e7-88639da4683d" # User.Read permission
az ad app permission add --id $APP_ID \
--api $GRAPH_RESOURCE_ID \
--api-permissions "$USER_READ_ID=Scope"
```
**Microsoft Graph Mail.Read (delegated):**
```bash
MAIL_READ_ID="570282fd-fa5c-430d-a7fd-fc8dc98a9dca" # Mail.Read permission
az ad app permission add --id $APP_ID \
--api $GRAPH_RESOURCE_ID \
--api-permissions "$MAIL_READ_ID=Scope"
```
**Microsoft Graph User.Read.All (application):**
```bash
USER_READ_ALL_ID="df021288-bdef-4463-88db-98f22de89214" # User.Read.All application permission
az ad app permission add --id $APP_ID \
--api $GRAPH_RESOURCE_ID \
--api-permissions "$USER_READ_ALL_ID=Role"
```
**Note:** Use `Scope` for delegated permissions, `Role` for application permissions.
### Common Permission IDs
**Microsoft Graph (00000003-0000-0000-c000-000000000000):**
| Permission | ID | Type |
|------------|-----|------|
| User.Read | e1fe6dd8-ba31-4d61-89e7-88639da4683d | Delegated |
| User.ReadWrite | b4e74841-8e56-480b-be8b-910348b18b4c | Delegated |
| Mail.Read | 570282fd-fa5c-430d-a7fd-fc8dc98a9dca | Delegated |
| Mail.Send | e383f46e-2787-4529-855e-0e479a3ffac0 | Delegated |
| Calendars.Read | 465a38f9-76ea-45b9-9f34-9e8b0d4b0b42 | Delegated |
| User.Read.All | df021288-bdef-4463-88db-98f22de89214 | Application |
| Directory.Read.All | 7ab1d382-f21e-4acd-a863-ba3e13f7da61 | Application |
### Grant admin consent
```bash
# Grant admin consent for all permissions
az ad app permission admin-consent --id $APP_ID
```
**Note:** Admin consent is required for application permissions and some delegated permissions.
### List permissions
```bash
az ad app permission list --id $APP_ID
```
### Delete permission
```bash
# Remove specific permission
az ad app permission delete --id $APP_ID \
--api $GRAPH_RESOURCE_ID \
--permission-id $USER_READ_ID
```
## Service Principal Management
### Create service principal
```bash
# Create service principal for the app
az ad sp create --id $APP_ID
```
### List service principals
```bash
az ad sp list --display-name "MyApp"
```
### Get service principal details
```bash
az ad sp show --id $APP_ID
```
### Delete service principal
```bash
az ad sp delete --id $APP_ID
```
## App Roles and Claims
### Get app roles
```bash
az ad app show --id $APP_ID --query "appRoles"
```
### Get optional claims
```bash
az ad app show --id $APP_ID --query "optionalClaims"
```
## Owners
### List app owners
```bash
az ad app owner list --id $APP_ID
```
### Add owner
```bash
# Add user as owner
USER_OBJECT_ID=$(az ad user show --id "user@domain.com" --query "id" -o tsv)
az ad app owner add --id $APP_ID --owner-object-id $USER_OBJECT_ID
```
### Remove owner
```bash
az ad app owner remove --id $APP_ID --owner-object-id $USER_OBJECT_ID
```
## Delete App Registration
```bash
# Delete app registration (and associated service principal)
az ad app delete --id $APP_ID
```
## Tenant and Identity Information
### Get tenant ID
```bash
az account show --query tenantId -o tsv
```
### Get current user information
```bash
az ad signed-in-user show
```
### Get user by email
```bash
az ad user show --id "user@domain.com"
```
### Get user object ID
```bash
az ad user show --id "user@domain.com" --query "id" -o tsv
```
### List all users
```bash
az ad user list --output table
```
## Scripting Examples
### Complete app setup script
```bash
#!/bin/bash
# Variables
APP_NAME="MyApplication"
REDIRECT_URI="http://localhost:3000"
echo "Creating app registration..."
APP_ID=$(az ad app create \
--display-name "$APP_NAME" \
--spa-redirect-uris "$REDIRECT_URI" \
--query "appId" -o tsv)
echo "App created with ID: $APP_ID"
echo "Adding Microsoft Graph permissions..."
GRAPH_RESOURCE_ID="00000003-0000-0000-c000-000000000000"
USER_READ_ID="e1fe6dd8-ba31-4d61-89e7-88639da4683d"
az ad app permission add --id $APP_ID \
--api $GRAPH_RESOURCE_ID \
--api-permissions "$USER_READ_ID=Scope"
echo "Granting admin consent..."
az ad app permission admin-consent --id $APP_ID
echo "Creating service principal..."
az ad sp create --id $APP_ID
TENANT_ID=$(az account show --query tenantId -o tsv)
echo ""
echo "App registration complete!"
echo "Application (Client) ID: $APP_ID"
echo "Tenant ID: $TENANT_ID"
echo "Redirect URI: $REDIRECT_URI"
```
### Cleanup script
```bash
#!/bin/bash
# Delete all apps matching pattern
az ad app list --display-name "Test*" --query "[].appId" -o tsv | while read APP_ID; do
echo "Deleting app: $APP_ID"
az ad app delete --id $APP_ID
done
```
references/console-app-example.md
# Console Application Examples
This document provides complete working examples of console applications that authenticate with Microsoft Entra ID using MSAL (Microsoft Authentication Library).
## Table of Contents
- [C# (.NET) Example](#c-net-example)
- [Python Example](#python-example)
- [JavaScript (Node.js) Example](#javascript-nodejs-example)
## C# (.NET) Example
### Prerequisites
```bash
dotnet new console -n EntraAuthConsole
cd EntraAuthConsole
dotnet add package Microsoft.Identity.Client
```
### Complete Code
```csharp
using Microsoft.Identity.Client;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace EntraAuthConsole
{
class Program
{
// Configuration - replace with your values
private const string ClientId = "YOUR_APPLICATION_CLIENT_ID";
private const string TenantId = "YOUR_TENANT_ID";
private static readonly string[] Scopes = new[] { "User.Read" };
static async Task Main(string[] args)
{
try
{
// Build the MSAL client
var app = PublicClientApplicationBuilder
.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.WithRedirectUri("http://localhost")
.Build();
// Try to get token silently from cache first
var accounts = await app.GetAccountsAsync();
AuthenticationResult result;
try
{
result = await app.AcquireTokenSilent(Scopes, accounts.FirstOrDefault())
.ExecuteAsync();
Console.WriteLine("Token acquired from cache");
}
catch (MsalUiRequiredException)
{
// Interactive authentication required
result = await app.AcquireTokenInteractive(Scopes)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
Console.WriteLine("Token acquired interactively");
}
// Display user information
Console.WriteLine($"\nWelcome, {result.Account.Username}!");
Console.WriteLine($"Token expires: {result.ExpiresOn}");
// Call Microsoft Graph API
await CallGraphApiAsync(result.AccessToken);
}
catch (MsalException ex)
{
Console.WriteLine($"Error acquiring token: {ex.Message}");
}
}
private static async Task CallGraphApiAsync(string accessToken)
{
using var httpClient = new System.Net.Http.HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var response = await httpClient.GetAsync("https://graph.microsoft.com/v1.0/me");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine("\nUser profile from Microsoft Graph:");
Console.WriteLine(content);
}
else
{
Console.WriteLine($"API call failed: {response.StatusCode}");
}
}
}
}
```
### Run the Application
```bash
dotnet run
```
### Device Code Flow (for headless scenarios)
```csharp
// Use this for servers or devices without a browser
result = await app.AcquireTokenWithDeviceCode(Scopes, deviceCodeResult =>
{
Console.WriteLine(deviceCodeResult.Message);
return Task.CompletedTask;
}).ExecuteAsync();
```
---
## Python Example
### Prerequisites
```bash
pip install msal requests
```
### Complete Code
```python
import msal
import requests
import json
# Configuration - replace with your values
CLIENT_ID = "YOUR_APPLICATION_CLIENT_ID"
TENANT_ID = "YOUR_TENANT_ID"
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
SCOPES = ["User.Read"]
def acquire_token_interactive():
"""Acquire token using interactive flow (opens browser)"""
app = msal.PublicClientApplication(
CLIENT_ID,
authority=AUTHORITY
)
# Try to get token from cache first
accounts = app.get_accounts()
result = None
if accounts:
# Try silent acquisition
result = app.acquire_token_silent(SCOPES, account=accounts[0])
if result:
print("Token acquired from cache")
if not result:
# Interactive authentication
result = app.acquire_token_interactive(
scopes=SCOPES,
prompt="select_account"
)
print("Token acquired interactively")
return result
def acquire_token_device_code():
"""Acquire token using device code flow (for headless scenarios)"""
app = msal.PublicClientApplication(
CLIENT_ID,
authority=AUTHORITY
)
flow = app.initiate_device_flow(scopes=SCOPES)
if "user_code" not in flow:
raise Exception(f"Failed to create device flow: {flow.get('error_description')}")
# Display instructions to user
print(flow["message"])
# Wait for user to complete authentication
result = app.acquire_token_by_device_flow(flow)
return result
def call_graph_api(access_token):
"""Call Microsoft Graph API with access token"""
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
response = requests.get(
'https://graph.microsoft.com/v1.0/me',
headers=headers
)
if response.status_code == 200:
user_data = response.json()
print("\nUser profile from Microsoft Graph:")
print(json.dumps(user_data, indent=2))
else:
print(f"API call failed: {response.status_code}")
print(response.text)
def main():
# Choose authentication method
print("Select authentication method:")
print("1. Interactive (opens browser)")
print("2. Device code (for headless scenarios)")
choice = input("Enter choice (1 or 2): ")
try:
if choice == "1":
result = acquire_token_interactive()
elif choice == "2":
result = acquire_token_device_code()
else:
print("Invalid choice")
return
if "access_token" in result:
print(f"\nWelcome, {result.get('id_token_claims', {}).get('preferred_username', 'User')}!")
print(f"Token expires in: {result.get('expires_in')} seconds")
# Call Microsoft Graph API
call_graph_api(result["access_token"])
else:
print(f"Error acquiring token: {result.get('error')}")
print(f"Description: {result.get('error_description')}")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
```
### Run the Application
```bash
python console_app.py
```
---
## JavaScript (Node.js) Example
### Prerequisites
```bash
npm init -y
npm install @azure/msal-node axios
```
### Complete Code
```javascript
const msal = require('@azure/msal-node');
const axios = require('axios');
// Configuration - replace with your values
const config = {
auth: {
clientId: "YOUR_APPLICATION_CLIENT_ID",
authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
}
};
const scopes = ["User.Read"];
// Interactive authentication (opens browser)
async function acquireTokenInteractive() {
const pca = new msal.PublicClientApplication(config);
const authCodeUrlParameters = {
scopes: scopes,
redirectUri: "http://localhost:3000",
};
// This opens the browser for authentication
const response = await pca.acquireTokenInteractive(authCodeUrlParameters);
return response;
}
// Device code flow (for headless scenarios)
async function acquireTokenDeviceCode() {
const pca = new msal.PublicClientApplication(config);
const deviceCodeRequest = {
deviceCodeCallback: (response) => {
console.log("\n" + response.message);
},
scopes: scopes,
};
const response = await pca.acquireTokenByDeviceCode(deviceCodeRequest);
return response;
}
// Client credentials flow (service-to-service, no user)
async function acquireTokenClientCredentials() {
const confidentialConfig = {
auth: {
clientId: "YOUR_APPLICATION_CLIENT_ID",
authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
clientSecret: "YOUR_CLIENT_SECRET", // From app registration
}
};
const cca = new msal.ConfidentialClientApplication(confidentialConfig);
const clientCredentialRequest = {
scopes: ["https://graph.microsoft.com/.default"],
};
const response = await cca.acquireTokenByClientCredential(clientCredentialRequest);
return response;
}
// Call Microsoft Graph API
async function callGraphApi(accessToken) {
const options = {
headers: {
Authorization: `Bearer ${accessToken}`
}
};
try {
const response = await axios.get('https://graph.microsoft.com/v1.0/me', options);
console.log('\nUser profile from Microsoft Graph:');
console.log(JSON.stringify(response.data, null, 2));
} catch (error) {
console.error('API call failed:', error.response?.status, error.message);
}
}
// Main function
async function main() {
console.log("Select authentication method:");
console.log("1. Device code flow (recommended for CLI)");
console.log("2. Client credentials (service-to-service)");
// For demonstration, using device code flow
// In production, get user input with readline or similar
const choice = "1";
try {
let result;
if (choice === "1") {
result = await acquireTokenDeviceCode();
} else if (choice === "2") {
result = await acquireTokenClientCredentials();
}
if (result.accessToken) {
console.log('\nAuthentication successful!');
console.log(`Token expires: ${new Date(result.expiresOn)}`);
// Call Microsoft Graph API
await callGraphApi(result.accessToken);
} else {
console.error('Failed to acquire token');
}
} catch (error) {
console.error('Error:', error.message);
}
}
main();
```
### Run the Application
```bash
node console_app.js
```
## Next Steps
- Review [oauth-flows.md](oauth-flows.md) for flow details
- See [api-permissions.md](api-permissions.md) for permission setup
- Check [troubleshooting.md](troubleshooting.md) for common issues
## Additional Resources
- [MSAL Libraries](https://learn.microsoft.com/entra/msal/)
references/first-app-registration.md
# First App Registration - Step-by-Step Guide
This guide walks you through creating your first Microsoft Entra app registration from scratch.
## Overview
You'll learn how to:
1. Create an app registration in Azure Portal
2. Configure authentication settings
3. Add API permissions
4. Create client credentials
5. Test the authentication flow
## Prerequisites
- Azure subscription (free tier works)
- Azure Portal access: https://portal.azure.com
- Basic understanding of your application type (web, mobile, service)
## Step 1: Navigate to App Registrations
1. Open [Azure Portal](https://portal.azure.com)
2. Search for **"Microsoft Entra ID"**
3. In the left menu, click **"App registrations"**
4. Click **"+ New registration"** at the top
## Step 2: Register Your Application
You'll see a form with several fields:
### Application Name
- **What to enter:** A descriptive name for your app
- **Example:** "My First Console App" or "Product Inventory API"
- **Tip:** Use a name that clearly identifies the purpose
### Supported Account Types
Choose who can use your application:
| Option | When to Use |
|--------|-------------|
| **Accounts in this organizational directory only (Single tenant)** | Only users from the same tenant of this app registration need access |
| **Accounts in any organizational directory (Multi-tenant)** | Users from multiple organization tenants need access |
| **Accounts in any organizational directory + Personal Microsoft accounts** | Users from multiple organization tenants and MSA users need access |
| **Personal Microsoft accounts only** | Only MSA users need access |
**Note:** Once selected, users whose account type is not allowed will get errors when trying to get access token for the app registration.
### Redirect URI (optional)
The redirect URI is where authentication responses are sent.
**Platform:** Select the type:
- **Web** - Server-side web apps
- **Single-page application (SPA)** - React, Angular, Vue apps
- **Public client/native** - Mobile, desktop, console apps
**URI examples:**
- Web app: `https://localhost:5001/signin-oidc`
- SPA: `http://localhost:3000`
- Console/Desktop: `http://localhost`
**For your first app:** Select **"Public client/native"** and enter `http://localhost`
### Click "Register"
After clicking, you'll be redirected to your app's overview page.
## Step 3: Save Important Information
On the **Overview** page, you'll see critical information. **Copy and save these values:**
### Application (client) ID
- **What it is:** Unique identifier for your app
- **Format:** `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (GUID)
- **When you need it:** Every time your app authenticates
- **Where to save:** Environment variables, configuration file
### Directory (tenant) ID
- **What it is:** Unique identifier for your Azure AD tenant
- **Format:** `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (GUID)
- **When you need it:** Constructing authentication URLs
### Example values to save:
```bash
# Save these in a secure location
APPLICATION_CLIENT_ID="12345678-1234-1234-1234-123456789012"
TENANT_ID="87654321-4321-4321-4321-210987654321"
```
## Step 4: Configure Authentication (Optional)
Click **"Authentication"** in the left menu.
### Advanced Settings
**Allow public client flows:**
- **What it is:** Enables device code flow, resource owner password flow
- **For console apps:** Turn this **ON**
- **For web apps:** Keep **OFF**
### Supported account types
You can change this later if needed.
### Logout URL (optional)
Where to redirect users after logout.
**Click "Save"** at the top if you made changes.
## Step 5: Add API Permissions
Click **"API permissions"** in the left menu.
### Default Permission
You'll see one default permission:
- **Microsoft Graph → User.Read (Delegated)**
This allows your app to read the signed-in user's profile.
### Add More Permissions
1. Click **"+ Add a permission"**
2. Select **"Microsoft Graph"**
3. Choose **"Delegated permissions"** (for user context)
4. Search for and select permissions you need:
- **User.Read** - Read user profile (already added)
- **Mail.Read** - Read user's mail
- **Calendars.Read** - Read user's calendar
5. Click **"Add permissions"**
### Admin Consent
Some permissions require admin consent:
- If you're an admin: Click **"Grant admin consent for [Your Org]"**
- If you're not: Ask your admin to grant consent
**Status indicator:**
- ✅ Green checkmark = Granted
- ⚠️ Yellow warning = Not granted (may still work for user consent)
## Step 6: Create Client Secret (If Needed)
**Skip this if:** You're building a desktop/mobile/console app (public client)
**Do this if:** You're building a web app, API, or service (confidential client)
1. Click **"Certificates & secrets"** in the left menu
2. Click **"+ New client secret"**
3. Enter a description: "Development Secret"
4. Choose expiration:
- **Recommended for development:** 6 months
- **For production:** 12-24 months (set up rotation)
5. Click **"Add"**
**⚠️ CRITICAL:** Copy the secret **Value** immediately!
- It's only shown once
- You cannot retrieve it later
- If you lose it, create a new one
```bash
# Save this securely (example)
CLIENT_SECRET="abc123~defGHI456jklMNO789pqrSTU"
```
**Security tips:**
- Never commit secrets to source control
- Use Azure Key Vault for production
- Use environment variables for development
## Step 7: Test Your App Registration
### Option A: Quick Test with Azure CLI
```bash
# Set your values
CLIENT_ID="your-client-id-here"
TENANT_ID="your-tenant-id-here"
# Interactive login
az login --scope "https://graph.microsoft.com/.default"
# Get an access token
az account get-access-token --resource "https://graph.microsoft.com"
```
### Option B: Test with MSAL Library
See the complete code example in [console-app-example.md](console-app-example.md)
### Expected Results
**Success:**
- Browser opens for authentication (or device code shown)
- You authenticate with your Azure AD account
- Access token is returned
- You can call Microsoft Graph API
**Common first-time issues:**
- Redirect URI mismatch → Double-check URI in Authentication settings
- Insufficient permissions → Add required API permissions
- User consent required → Grant admin consent or user must consent
**Tip:** Once you get the access token, you can use [jwt.ms](https://jwt.ms) to decode it and inspect its claims.
## Step 8: Review Configuration
### Checklist
- ✅ App registered with clear name
- ✅ Application ID and Tenant ID saved securely
- ✅ Redirect URI configured correctly
- ✅ API permissions added
- ✅ Admin consent granted (if required)
- ✅ Client secret created and saved (if needed)
- ✅ Authentication tested successfully
## Next Steps
- In your client app, implement the OAuth flow to acquire access tokens for your app registration.
- In your server app, implement token validation to protect your resources.
## Troubleshooting
### Redirect URI mismatch"
**Solution:**
- Check Authentication → Redirect URIs
- Ensure exact match (case-sensitive, trailing slash matters)
- Ensure correct platform (Web vs SPA vs Public client)
### User consent required
**Solution:**
- Grant admin consent in API permissions
- Or have user consent during first login
## Additional Resources
- [Microsoft Entra ID Documentation](https://learn.microsoft.com/en-us/entra/identity-platform/)
references/oauth-flows.md
# OAuth 2.0 Flows
This document provides an illustration of OAuth 2.0 authentication flows supported by Microsoft Entra ID.
**Note:** All the following implementation steps are for illustration purposes. It's always recommended to use a library to handle the authentication flow.
## Authorization Code Flow
### Flow Steps
```
1. User → App: Navigate to app's web UI
2. App → User: Redirect to Microsoft login
3. User → Entra ID: Authenticate & consent
4. Entra ID → App: Authorization code (via redirect URI)
5. App → Entra ID: Exchange code for tokens (with client secret)
6. Entra ID → App: Access token + refresh token + ID token
7. App → API: Call API with access token
```
### Implementation Steps
#### 1. Build Authorization URL
```
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id={application_id}
&response_type=code
&redirect_uri={redirect_uri}
&response_mode=query
&scope={scopes}
&state={random_state}
```
**Parameters:**
- `tenant`: Your tenant ID or `common` for multi-tenant
- `client_id`: Application (client) ID from app registration
- `redirect_uri`: Must match exactly what's registered
- `scope`: Space-separated permissions (e.g., `openid profile User.Read`)
- `state`: Random value to prevent CSRF attacks
#### 2. User Authenticates
User is redirected to Microsoft login page, authenticates, and grants consent.
#### 3. Receive Authorization Code
App receives callback at redirect URI:
```
https://your-app.com/callback?
code={authorization_code}
&state={state_value}
```
**Validation:**
- Verify `state` matches what you sent
- Extract `code` parameter
#### 4. Exchange Code for Tokens
```http
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={application_id}
&scope={scopes}
&code={authorization_code}
&redirect_uri={redirect_uri}
&grant_type=authorization_code
&client_secret={client_secret}
```
**Response:**
```json
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "M.R3_BAY...",
"id_token": "eyJ0eXAi..."
}
```
#### 5. Use Access Token
```http
GET https://graph.microsoft.com/v1.0/me
Authorization: Bearer {access_token}
```
## Authorization Code Flow with PKCE
PKCE (Proof Key for Code Exchange) adds security for public clients that cannot securely store a client secret.
### Flow Steps
```
1. App: Generate code verifier (random string)
2. App: Generate code challenge (SHA256 hash of verifier)
3. App → Entra ID: Authorization request with code challenge
4. User → Entra ID: Authenticate & consent
5. Entra ID → App: Authorization code
6. App → Entra ID: Exchange code + code verifier for token
7. Entra ID: Validates verifier matches challenge
8. Entra ID → App: Access token + ID token
```
### Implementation Steps
#### 1. Generate PKCE Values
**Code Verifier:** 43-128 character random string
```javascript
// JavaScript example
const codeVerifier = generateRandomString(128);
```
**Code Challenge:** Base64URL-encoded SHA256 hash of verifier
```javascript
const codeChallenge = base64URLEncode(sha256(codeVerifier));
```
#### 2. Build Authorization URL
```
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id={application_id}
&response_type=code
&redirect_uri={redirect_uri}
&scope={scopes}
&state={state}
&code_challenge={code_challenge}
&code_challenge_method=S256
```
#### 3. Exchange Code for Tokens (No Secret)
```http
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={application_id}
&scope={scopes}
&code={authorization_code}
&redirect_uri={redirect_uri}
&grant_type=authorization_code
&code_verifier={code_verifier}
```
## Client Credentials Flow
### Flow Steps
```
1. App → Entra ID: Request token with client ID + secret
2. Entra ID: Validate credentials
3. Entra ID → App: Access token (application permissions)
4. App → API: Call API with token
```
### Implementation Steps
#### 1. Configure Application Permissions
In app registration:
1. Go to "API permissions"
2. Add **Application** permissions (not delegated)
3. Grant admin consent (required for app permissions)
**Example permissions:**
- `User.Read.All` (application) - Read all users
- `Directory.Read.All` (application) - Read directory
#### 2. Request Access Token
```http
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={application_id}
&scope=https://graph.microsoft.com/.default
&client_secret={client_secret}
&grant_type=client_credentials
```
**Parameters:**
- `scope`: Use `{resource}/.default` format
- For Microsoft Graph: `https://graph.microsoft.com/.default`
- For your API: `api://{api_app_id}/.default`
**Response:**
```json
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3599
}
```
#### 3. Use Access Token
```http
GET https://graph.microsoft.com/v1.0/users
Authorization: Bearer {access_token}
```
## Device Code Flow
**Use for:** Devices without browsers (IoT, CLIs), headless environments
### Flow Steps
```
1. App → Entra ID: Request device code
2. Entra ID → App: Device code + user code + verification URL
3. App → User: Display code and URL
4. User: Opens URL on another device, enters code
5. User → Entra ID: Authenticates & consents
6. App → Entra ID: Poll for token
7. Entra ID → App: Access token (after user completes auth)
```
### Implementation Steps
#### 1. Request Device Code
```http
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/devicecode
Content-Type: application/x-www-form-urlencoded
client_id={application_id}
&scope={scopes}
```
**Response:**
```json
{
"user_code": "GTHK-QPMN",
"device_code": "GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8",
"verification_uri": "https://microsoft.com/devicelogin",
"expires_in": 900,
"interval": 5,
"message": "To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code GTHK-QPMN to authenticate."
}
```
#### 2. Display Instructions to User
```
To sign in, open https://microsoft.com/devicelogin
and enter code: GTHK-QPMN
```
#### 3. Poll for Token
```http
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={application_id}
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code={device_code}
```
**Poll every 5 seconds (use `interval` from response)**
**Pending Response (user hasn't completed auth yet):**
```json
{
"error": "authorization_pending",
"error_description": "AADSTS70016: Pending end-user authorization..."
}
```
**Success Response:**
```json
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "M.R3_BAY...",
"id_token": "eyJ0eXAi..."
}
```
## Refresh Token Flow
**Use for:** Refreshing expired access tokens without re-authentication
### When to Refresh
- Access tokens typically expire in 1 hour
- Refresh tokens are long-lived (14-90 days)
- Refresh before access token expires for seamless UX
### Implementation
```http
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={application_id}
&scope={scopes}
&refresh_token={refresh_token}
&grant_type=refresh_token
&client_secret={client_secret}
```
**Note:** `client_secret` only required for confidential clients
**Response:**
```json
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "M.R3_BAY...",
"id_token": "eyJ0eXAi..."
}
```
**Important:** New refresh token is returned; use it for next refresh
## Token Types
### Access Token
- Used to call APIs
- Contains claims (user ID, permissions, etc.)
- Short-lived (typically 1 hour)
- Format: JWT (JSON Web Token)
**Sample claims:**
```json
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/{tenant}/",
"sub": "{user_object_id}",
"scp": "User.Read Mail.Read",
"exp": 1680000000
}
```
### Refresh Token
- Used to get new access tokens
- Long-lived (days to months)
- Opaque string (not JWT)
- Single-use (new one issued with each refresh)
### ID Token
- Contains user identity information
- Used by the app to authenticate user
- Format: JWT
**Sample claims:**
```json
{
"sub": "{user_object_id}",
"name": "Jane Doe",
"preferred_username": "jane@contoso.com",
"email": "jane@contoso.com",
"oid": "{object_id}"
}
```
## Scopes and Permissions
### Scope Format
**Microsoft Graph:**
```
https://graph.microsoft.com/User.Read
https://graph.microsoft.com/Mail.Send
```
**Custom API:**
```
api://{api_application_id}/access_as_user
```
## Security Considerations
| Practice | Why |
|----------|-----|
| **Use state parameter** | Prevents CSRF attacks |
| **Use PKCE for public clients** | Prevents authorization code interception |
| **Validate tokens** | Verify signature, issuer, audience, expiration |
| **Use HTTPS only** | Protect tokens in transit |
| **Store tokens securely** | Use secure storage, never in localStorage for sensitive apps |
| **Implement token refresh** | Seamless UX without repeated logins |
| **Handle token expiration** | Gracefully refresh or re-authenticate |
| **Minimal scope principle** | Request only necessary permissions |
## Additional Resources
[OAuth 2.0 spec](https://www.rfc-editor.org/rfc/rfc6749)
references/sdk/azure-identity-dotnet.md
# Authentication — .NET SDK Quick Reference
> Condensed from **azure-identity-dotnet**. Full patterns (ASP.NET DI,
> sovereign clouds, brokered auth, certificate credentials)
> in the **azure-identity-dotnet** plugin skill if installed.
## Install
dotnet add package Azure.Identity
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```csharp
using Azure.Identity;
var credential = new DefaultAzureCredential();
```
## Best Practices
- Use DefaultAzureCredential for **local development only**. In production, use deterministic credentials (ManagedIdentityCredential) — see [auth-best-practices.md](../auth-best-practices.md)
- Reuse credential instances — single instance shared across clients
- Configure retry policies for credential operations
- Enable logging with AzureEventSourceListener for debugging auth issues
references/sdk/azure-identity-java.md
# Authentication — Java SDK Quick Reference
> Condensed from **azure-identity-java**. Full patterns (workload identity,
> certificate auth, device code, sovereign clouds)
> in the **azure-identity-java** plugin skill if installed.
## Install
```xml
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.15.0</version>
</dependency>
```
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```java
import com.azure.identity.DefaultAzureCredentialBuilder;
var credential = new DefaultAzureCredentialBuilder().build();
```
## Best Practices
- Use DefaultAzureCredential for **local development only** (CLI, PowerShell, VS Code). In production, use ManagedIdentityCredential — see [auth-best-practices.md](../auth-best-practices.md)
- Managed identity in production — no secrets to manage, automatic rotation
- Azure CLI for local dev — run `az login` before running your app
- Least privilege — grant only required permissions to service principals
- Token caching — enabled by default, reduces auth round-trips
- Environment variables — use for CI/CD, not hardcoded secrets
references/sdk/azure-identity-py.md
# Authentication — Python SDK Quick Reference
> Condensed from **azure-identity-py**. Full patterns (async,
> ChainedTokenCredential, token caching, all credential types)
> in the **azure-identity-py** plugin skill if installed.
## Install
```bash
pip install azure-identity
```
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```python
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
```
## Best Practices
- Use DefaultAzureCredential for **local development only** (CLI, PowerShell, VS Code). In production, use ManagedIdentityCredential — see [auth-best-practices.md](../auth-best-practices.md)
- Never hardcode credentials — use environment variables or managed identity
- Prefer managed identity in production Azure deployments
- Use ChainedTokenCredential when you need a custom credential order
- Close async credentials explicitly or use context managers
- Set AZURE_CLIENT_ID env var for user-assigned managed identities
- Exclude unused credentials to speed up authentication
references/sdk/azure-identity-rust.md
# Authentication — Rust SDK Quick Reference
> Condensed from **azure-identity-rust**. Full patterns (ClientSecret,
> ClientCertificate, WorkloadIdentity, AzurePipelines credentials)
> in the **azure-identity-rust** plugin skill if installed.
## Install
cargo add azure_identity
## Quick Start
```rust
use azure_identity::DeveloperToolsCredential;
let credential = DeveloperToolsCredential::new(None)?;
```
## Best Practices
- Use DeveloperToolsCredential for local dev — automatically picks up Azure CLI
- Use ManagedIdentityCredential in production — no secrets to manage
- Clone credentials — credentials are Arc-wrapped and cheap to clone
- Reuse credential instances — same credential can be used with multiple clients
- Use tokio feature — `cargo add azure_identity --features tokio`
references/sdk/azure-identity-ts.md
# Authentication — TypeScript SDK Quick Reference
> Condensed from **azure-identity-ts**. Full patterns (sovereign clouds,
> device code flow, custom credentials, bearer token provider)
> in the **azure-identity-ts** plugin skill if installed.
## Install
npm install @azure/identity
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```typescript
import { DefaultAzureCredential } from "@azure/identity";
const credential = new DefaultAzureCredential();
```
## Best Practices
- Use DefaultAzureCredential for **local development only** (CLI, PowerShell, VS Code). In production, use ManagedIdentityCredential — see [auth-best-practices.md](../auth-best-practices.md)
- Never hardcode credentials — use environment variables or managed identity
- Prefer managed identity — no secrets to manage in production
- Scope credentials appropriately — use user-assigned identity for multi-tenant scenarios
- Handle token refresh — Azure SDK handles this automatically
- Use ChainedTokenCredential for custom fallback scenarios
references/sdk/azure-keyvault-py.md
# Key Vault — Python SDK Quick Reference
> Condensed from **azure-keyvault-py**. Full patterns (async clients,
> cryptographic operations, certificate management, error handling)
> in the **azure-keyvault-py** plugin skill if installed.
## Install
pip install azure-keyvault-secrets azure-keyvault-keys azure-keyvault-certificates azure-identity
## Quick Start
```python
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
client = SecretClient(vault_url="https://<vault>.vault.azure.net/", credential=DefaultAzureCredential())
```
## Best Practices
- Use DefaultAzureCredential for **local development only**. In production, use ManagedIdentityCredential — see [auth-best-practices.md](../auth-best-practices.md)
- Use managed identity in Azure-hosted applications
- Enable soft-delete for recovery (enabled by default)
- Use RBAC over access policies for fine-grained control
- Rotate secrets regularly using versioning
- Use Key Vault references in App Service/Functions config
- Cache secrets appropriately to reduce API calls
- Use async clients for high-throughput scenarios
references/sdk/azure-keyvault-secrets-ts.md
# Key Vault Secrets — TypeScript SDK Quick Reference
> Condensed from **azure-keyvault-secrets-ts**. Full patterns (key rotation,
> cryptographic operations, backup/restore, wrap/unwrap)
> in the **azure-keyvault-secrets-ts** plugin skill if installed.
## Install
npm install @azure/keyvault-secrets @azure/identity
## Quick Start
```typescript
import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
const client = new SecretClient("https://<vault>.vault.azure.net", new DefaultAzureCredential());
```
## Best Practices
- Use DefaultAzureCredential for **local development only**. In production, use ManagedIdentityCredential — see [auth-best-practices.md](../auth-best-practices.md)
- Enable soft-delete — required for production vaults
- Set expiration dates on both keys and secrets
- Use key rotation policies — automate key rotation
- Limit key operations — only grant needed operations (encrypt, sign, etc.)
- Browser not supported — these SDKs are Node.js only
references/sdk/microsoft-azure-webjobs-extensions-authentication-events-dotnet.md
# Authentication Events — .NET SDK Quick Reference
> Condensed from **microsoft-azure-webjobs-extensions-authentication-events-dotnet**.
> Full patterns (attribute collection, OTP customization, external data enrichment)
> in the source plugin skill if installed.
## Install
dotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents
## Quick Start
```csharp
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;
[FunctionName("OnTokenIssuanceStart")]
public static WebJobsAuthenticationEventResponse Run(
[WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,
ILogger log)
{
var response = new WebJobsTokenIssuanceStartResponse();
response.Actions.Add(new WebJobsProvideClaimsForToken
{
Claims = new Dictionary<string, string> { { "claim", "value" } }
});
return response;
}
```
## Best Practices
- Validate all inputs — never trust request data; validate before processing
- Handle errors gracefully — return appropriate error responses, don't throw
- Log correlation IDs — use CorrelationId for troubleshooting
- Keep functions fast — authentication events have timeout limits
- Use managed identity — access Azure resources securely
- Cache external data — avoid slow lookups on every request
- Test locally — use Azure Functions Core Tools with sample payloads
- Monitor with App Insights — track function execution and errors
references/troubleshooting.md
# Troubleshooting Microsoft Entra App Registration
This guide helps you diagnose and fix common issues with app registrations and authentication.
## Table of Contents
- [Authentication Errors](#authentication-errors)
- [Token Issues](#token-issues)
- [Permission Problems](#permission-problems)
- [Redirect URI Issues](#redirect-uri-issues)
- [Application Configuration](#application-configuration)
- [Debugging Tools](#debugging-tools)
## Authentication Errors
### Redirect URI Mismatch
**Error message:**
```
AADSTS50011: The redirect URI 'http://localhost:3000' specified in the request
does not match the redirect URIs configured for the application.
```
**Cause:** The redirect URI in your authentication request doesn't exactly match what's registered.
**Solutions:**
1. **Check exact match** (case-sensitive, trailing slash matters):
```
Registered: https://myapp.com/callback
Request: https://myapp.com/callback/ ❌ (trailing slash)
Request: https://MyApp.com/callback ❌ (case difference)
Request: https://myapp.com/callback ✅
```
2. **Add URI to app registration:**
```bash
# Portal: Authentication → Add redirect URI
# CLI:
az ad app update --id $APP_ID \
--web-redirect-uris "http://localhost:3000" "https://myapp.com/callback"
```
3. **Check platform type:**
- Web URIs go in "Web" platform
- SPA URIs go in "Single-page application"
- Desktop/mobile URIs go in "Public client/native"
### Invalid Client Secret
**Error message:**
```
AADSTS7000215: Invalid client secret provided.
Ensure the secret being sent in the request is the client secret value, not the client secret ID.
```
**Causes:**
- Client secret expired
- Wrong secret value (copied secret ID instead of value)
- Secret doesn't match app registration
**Solutions:**
1. **Check expiration:**
```bash
az ad app credential list --id $APP_ID
```
2. **Create new secret:**
```bash
az ad app credential reset --id $APP_ID --years 1
```
Copy the `password` value (not the `keyId`)
### User Consent Required
**Error message:**
```
AADSTS65001: The user or administrator has not consented to use the application
```
**Causes:**
- Application permissions require admin consent
- User hasn't consented to delegated permissions
- Consent was revoked
**Solutions:**
1. **Grant admin consent (if admin):**
```bash
az ad app permission admin-consent --id $APP_ID
```
2. **Request user consent (interactive flow):**
This requires the client app to have access to UI such as browser, terminal window, etc. Follow the best practices of your client app to implement the interactive flow.
3. **Check API permissions in portal:**
- Ensure permissions are added
- Look for green checkmarks (granted)
- Yellow warning means not granted
### Grant Declined
**Error message:**
```
AADSTS70000: The request was denied because one or more permissions have been declined
```
**Cause:** User or admin explicitly denied consent.
**Solutions:**
1. **Re-request with explanation:**
- Explain why permissions are needed
- Request only necessary permissions
2. **Check if admin consent is required:**
- Some organizations disable user consent
- Contact your admin to grant consent
3. **Reduce permission scope:**
- Request minimal permissions initially
- Use incremental consent for additional features
### Application Not Found
**Error message:**
```
AADSTS700016: Application with identifier '{app-id}' was not found in the directory
```
**Causes:**
- Wrong application ID
- Wrong tenant ID
- Service principal not created
- App in different tenant
**Solutions:**
1. **Verify application ID:**
```bash
az ad app list --display-name "MyApp" --query "[].{Name:displayName, AppId:appId}"
```
2. **Verify tenant ID:**
```bash
az account show --query tenantId -o tsv
```
### Application Doesn't have a Service Principal
**Error message:**
```
The app is trying to access a service 'your_app_id'(your_app_name) that your organization 'your_tenant_id' lacks a service principal for
```
**Causes:**
- Your tenant is not configured to automatically provision the service principal for app registrations in it.
**Solutions:**
1. **Create service principal:**
```bash
az ad sp create --id $APP_ID
```
### Missing Required Field
**Error message:**
```
AADSTS90014: The required field 'client_id' is missing from the request
```
This can happen if the client you are using isn't compatible with Entra. Consult the owner of your client app to see if it supports Entra.
## Token Issues
Unless the the access token is encrypted, you can decode and view its claims securely at https://jwt.ms. **Don't** use any other website to decode an access token. Compare the claims in the token with the app registration's configuration to identify issues.
## Debugging Tools
### JWT Token Decoder
**Tool:** https://jwt.ms
**How to use:**
1. Copy your access token
2. Paste into jwt.ms
3. Review claims:
- `aud` - Audience (should match your API)
- `iss` - Issuer (should be login.microsoftonline.com)
- `scp` - Delegated permissions
- `roles` - Application permissions
- `exp` - Expiration timestamp
- `oid` - User object ID
---
### Fiddler
**Use for:** Inspecting HTTP requests/responses
**What to check:**
- Authorization header format: `Bearer {token}`
- Token is being sent
- Response status codes and error messages
### Entra Sign-in Logs
**Access:** Azure Portal → Microsoft Entra ID → Sign-in logs
**What to check:**
- Failed sign-in attempts
- Error codes and messages
- User consent status
- Conditional Access policy failures
## Common Error Codes Reference
| Error Code | Meaning | Common Cause |
|------------|---------|--------------|
| AADSTS50011 | Redirect URI mismatch | URI not registered or doesn't match |
| AADSTS50020 | Invalid tenant | Wrong tenant in authority URL |
| AADSTS50034 | User not found | User doesn't exist in tenant |
| AADSTS50053 | Account locked | Too many failed attempts |
| AADSTS50055 | Password expired | User needs to reset password |
| AADSTS50057 | Account disabled | User account disabled |
| AADSTS50058 | Silent sign-in failed | Interactive auth required |
| AADSTS50059 | Tenant not found | Invalid tenant ID |
| AADSTS65001 | Consent required | User/admin hasn't consented |
| AADSTS70000 | Grant declined | User denied consent |
| AADSTS70001 | App disabled | App registration disabled |
| AADSTS700016 | App not found | Invalid app ID or wrong tenant |
| AADSTS7000215 | Invalid client secret | Wrong/expired secret |
| AADSTS90014 | Missing field | Required parameter not sent |
| AADSTS90072 | Consent needed | Admin consent required |
## Best Practices for Troubleshooting
### Systematic Approach
1. **Collect information:**
- Exact error message and code
- When it started happening
- What changed recently
- Environment (dev/test/prod)
2. **Check basics first:**
- App ID and tenant ID correct
- Permissions added and consented
- Redirect URIs configured
- Secrets/certificates valid
3. **Use debugging tools:**
- Decode tokens (jwt.ms)
- Check sign-in logs
- Enable MSAL logging
- Use network inspector
4. **Test incrementally:**
- Test with minimal permissions
- Add permissions one at a time
- Test different flows separately
## Getting Help
### Microsoft Resources
- [Microsoft Q&A](https://learn.microsoft.com/answers/)
- [Microsoft Identity Platform Documentation](https://learn.microsoft.com/entra/identity-platform/)