Authentication & SSO (Microsoft Entra ID)
The platform uses Microsoft Entra ID (formerly Azure AD) as its single identity provider. All APIs and the web UI authenticate via OAuth 2.0 / OpenID Connect tokens, and it plugs straight into Microsoft 365 / Teams. This page covers tenant setup, app registration, token acquisition, the On-Behalf-Of flow, token refresh, and sample ID/access-token claims.
1. Setting up Microsoft Entra ID
The Entra ID admin needs to prepare three things:
- Tenant ID (e.g. contoso.onmicrosoft.com)
- App registrations (one for the Web app, and a separate mobile/desktop registration if you use Teams SSO)
- Client ID / client secret (Web app only)
■ Redirect URI
Register the following as the Web-app redirect URI:
2. Authorization code flow (standard Web app)
Browser sign-in uses OAuth 2.0 Authorization Code Flow. The ASP.NET Core middleware (Microsoft.Identity.Web) handles this for you — no manual implementation required on the application side.
■ Flow overview
- 1. User hits /signin
- 2. Redirected to the Entra ID sign-in page
- 3. Authorization code returns to the redirect URI
- 4. Server exchanges the code for access + ID + refresh tokens
- 5. Tokens are stored in the session cookie
// Program.cs / Startup.cs
builder.Services
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
3. Acquiring and using tokens
Use the server-held access token when calling APIs. Expired tokens are refreshed automatically via the refresh token.
■ C# (Microsoft.Identity.Web) — calling a downstream API
public class ProjectsController : Controller
{
private readonly ITokenAcquisition _tokens;
public async Task<IActionResult> Index()
{
var token = await _tokens.GetAccessTokenForUserAsync(
new[] { "api://{ClientId}/access_as_user" });
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var res = await client.GetAsync("https://api.yourdomain.com/api/projects");
return Ok(await res.Content.ReadAsStringAsync());
}
}
■ curl — hitting the API with the acquired token
curl https://api.yourdomain.com/api/projects \ -H "Authorization: Bearer eyJ0eXAiOiJKV1Qi..."
4. On-Behalf-Of flow (Teams SSO → API)
To call the platform APIs from a Microsoft Teams tab app while preserving the user context, use the On-Behalf-Of (OBO) flow.
- 1. The Teams client obtains an SSO token via getAuthToken()
- 2. The token is scoped to the Teams app registration's client ID
- 3. The server exchanges that token via OBO for an access token targeted at the platform API
- 4. The server calls the API with that access token
■ C# — OBO token exchange
// Receive the Teams SSO token in Authorization header,
// exchange for a platform-API token via OBO.
var teamsToken = Request.Headers["Authorization"]
.ToString().Replace("Bearer ", "");
var platformToken = await _tokens.GetAccessTokenForUserAsync(
scopes: new[] { "api://{PlatformApiClientId}/access_as_user" },
userFlow: null,
authenticationScheme: OpenIdConnectDefaults.AuthenticationScheme,
user: null,
tokenAcquisitionOptions: new TokenAcquisitionOptions
{
LongRunningWebApiSessionKey = TokenAcquisitionOptions.LongRunningWebApiSessionKeyAuto
});
5. Audience (aud) and scopes
Access tokens targeting the API must carry the correct audience for that resource.
The platform's API audience is api://{ClientId} (where ClientId is the Web API app registration's value).
■ Common scopes
access_as_user— access_as_user — delegated user access to all API endpointsUser.Read— User.Read — minimum scope for Microsoft Graph (profile / email)
6. Token refresh
Access tokens expire after ~1 hour by default. With Microsoft.Identity.Web they're refreshed automatically using the refresh token. If you handle refresh tokens manually, follow these rules:
- Keep refresh tokens server-side only — never return them to the browser
- Don't use the same refresh_token concurrently (rotation detection invalidates them)
- On invalid_grant, always redirect to the sign-in page
7. Sample ID and access token claims
The most useful claims to decode in your application. Samples below are real shapes with some values masked.
■ ID token — sample claims
{
"aud": "{ClientId}",
"iss": "https://login.microsoftonline.com/{TenantId}/v2.0",
"iat": 1737123456,
"exp": 1737127056,
"name": "山田 太郎",
"preferred_username": "yamada@contoso.onmicrosoft.com",
"oid": "00000000-0000-0000-0000-000000000000",
"tid": "{TenantId}",
"ver": "2.0"
}
■ Access token — sample claims
{
"aud": "api://{ClientId}",
"iss": "https://sts.windows.net/{TenantId}/",
"iat": 1737123456,
"exp": 1737127056,
"scp": "access_as_user",
"oid": "00000000-0000-0000-0000-000000000000",
"tid": "{TenantId}",
"ver": "2.0"
}
8. Common errors and fixes
- aud mismatch → make sure you explicitly request the API app registration's scope (e.g. access_as_user)
- AADSTS65001 (consent_required) → tenant admin consent isn't granted yet. Have the admin grant consent once
- Signature validation fails → check the public-key cache (Microsoft.Identity.Web usually refreshes it automatically)
- Repeated 401 Unauthorized → the access token is probably expired; refresh and retry
Next: REST API docs →