Chapter 31 · Entra App Registration
Subchapter 31.7
references/oauth-flows.mdMarkdown9 KBView on GitHub
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.
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 tokenhttps://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-tenantclient_id: Application (client) ID from app registrationredirect_uri: Must match exactly what’s registeredscope: Space-separated permissions (e.g., openid profile User.Read)state: Random value to prevent CSRF attacksUser is redirected to Microsoft login page, authenticates, and grants consent.
App receives callback at redirect URI:
https://your-app.com/callback?
code={authorization_code}
&state={state_value}Validation:
state matches what you sentcode parameterPOST 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:
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "M.R3_BAY...",
"id_token": "eyJ0eXAi..."
}GET https://graph.microsoft.com/v1.0/me
Authorization: Bearer {access_token}PKCE (Proof Key for Code Exchange) adds security for public clients that cannot securely store a client secret.
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 tokenCode Verifier: 43-128 character random string
// JavaScript example
const codeVerifier = generateRandomString(128);Code Challenge: Base64URL-encoded SHA256 hash of verifier
const codeChallenge = base64URLEncode(sha256(codeVerifier));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=S256POST 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}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 tokenIn app registration:
Example permissions:
User.Read.All (application) - Read all usersDirectory.Read.All (application) - Read directoryPOST 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_credentialsParameters:
scope: Use {resource}/.default format
https://graph.microsoft.com/.defaultapi://{api_app_id}/.defaultResponse:
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3599
}GET https://graph.microsoft.com/v1.0/users
Authorization: Bearer {access_token}Use for: Devices without browsers (IoT, CLIs), headless environments
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)POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/devicecode
Content-Type: application/x-www-form-urlencoded
client_id={application_id}
&scope={scopes}Response:
{
"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."
}To sign in, open https://microsoft.com/devicelogin
and enter code: GTHK-QPMNPOST 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):
{
"error": "authorization_pending",
"error_description": "AADSTS70016: Pending end-user authorization..."
}Success Response:
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "M.R3_BAY...",
"id_token": "eyJ0eXAi..."
}Use for: Refreshing expired access tokens without re-authentication
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:
{
"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
Sample claims:
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/{tenant}/",
"sub": "{user_object_id}",
"scp": "User.Read Mail.Read",
"exp": 1680000000
}Sample claims:
{
"sub": "{user_object_id}",
"name": "Jane Doe",
"preferred_username": "jane@contoso.com",
"email": "jane@contoso.com",
"oid": "{object_id}"
}Microsoft Graph:
https://graph.microsoft.com/User.Read
https://graph.microsoft.com/Mail.SendCustom API:
api://{api_application_id}/access_as_user| 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 |