How to Implement OAuth 2.0 and OpenID Connect in ASP.NET Core 10 Using Microsoft Entra ID (2026)
OAuth 2.0 lets a client obtain permission to call an API. OpenID Connect (OIDC) adds a standard way for that client to authenticate a user. In this lab, a desktop console client signs in through Microsoft Entra ID with authorization code and PKCE, then sends an access token to an ASP.NET Core 10 API. The API validates the token and checks for an order-reading scope before returning a response.
The two app registrations matter. If you configure the API to accept a token intended for the sign-in client or Microsoft Graph, you lose the boundary the audience check is meant to enforce. This tutorial uses a single tenant and two application IDs so you can inspect that boundary yourself.
How the identity flow works
| Item | Purpose | Receiver |
|---|---|---|
| Authorization code | One-time value exchanged with a PKCE verifier | Registered client |
| ID token | Identity information for the signed-in client | OIDC client |
| Access token | Delegated permission to a particular API | Resource API |
| Scope | Permission delegated to the client on the user’s behalf | Checked by API |
PKCE binds the authorization code to the client instance that initiated the request. The client keeps a verifier and sends a derived challenge with the authorization request. MSAL’s interactive flow handles the exchange. The client opens a browser, which lets Entra apply consent and tenant sign-in policies. The API only sees the resulting bearer token. Microsoft authorization code flow
Register the API and client
Use the .NET 10 SDK, an Entra tenant where you can register apps and consent to the lab scope, and a trusted development HTTPS certificate. Create two single-tenant registrations.
- Register Orders API. Record its Application (client) ID and Directory (tenant) ID. Under Expose an API, set the Application ID URI to the default
api://<api-client-id>. Create an enabled delegated scope namedOrders.Readwith descriptions suitable for your tenant. Set the API’s requested access token version to 2 in its manifest if it is not already configured. - Register Orders Desktop Client. Under Authentication, add the Mobile and desktop applications platform with the
http://localhostredirect URI. This is a public client and has no client secret. Keep the loopback redirect for the browser-based desktop development scenario. - In the client’s API permissions, select the Orders API and add delegated
Orders.Read. Grant consent using the tenant’s approved process. If the tenant blocks user consent, ask its administrator to approve the permission. - Keep the two application IDs separate in your terminal settings. The API uses the API ID. The desktop client uses its own ID for sign-in and the API ID when constructing the scope.
Expose a web API · MSAL interactive acquisition
Build the ASP.NET Core API
Create a web project named Orders.Api targeting net10.0. Add Microsoft.Identity.Web from NuGet. Use the stable version resolved for the project and commit the resulting package reference. Replace its Program.cs with this file. Set AzureAd__Instance to https://login.microsoftonline.com/, AzureAd__TenantId to the Directory ID, and AzureAd__ClientId to the Orders API ID. The default audience configuration covers the v2 token setup above. Check the documented audience settings if you deliberately change token version or use a different resource identifier.
Bearer validation checks the token’s signature, issuer, audience and expiration using the configured identity provider. Scope verification is a separate step. Orders.Read allows a caller to use this route. A real order service also needs to check which tenant or account may read a particular order. Use the validated issuer and stable subject identifier or your internal account ID for that decision. Email addresses and display names can change.
Microsoft protected web API configuration · JWT bearer validation
Sign in and call the route
Create a second console project named Orders.Client targeting net10.0 and add Microsoft.Identity.Client. Paste the following into its Program.cs. Set the three ENTRA values and ORDERS_URL to the local HTTPS /api/orders endpoint. Run the API and client in separate terminals. Do not print or persist access tokens in this lab.
The expected success response is HTTP 200 with [{"id":101,"status":"Pending"}]. An anonymous GET should return 401. A valid token for the API with a different delegated scope should receive 403. To test the last case, expose a second scope, consent to it, and request only that scope in a fresh session. Use a token for Microsoft Graph to check that a wrong audience returns 401. These are expected results for the configuration shown. The Entra sign-in flow was not exercised here.
Diagnose common failures
| Symptom | Check |
|---|---|
| Browser reports a redirect mismatch | Ensure the desktop app registration uses the loopback redirect expected by MSAL. |
| Client reports that consent is required | Check delegated API permission and tenant consent policy. |
| API responds 401 after sign-in | Check that you sent the access token, then check the issuer, audience and lifetime. |
| API responds 403 | Check that the access token has the API’s delegated scope and that any resource policy allows the request. |
Avoid logging JWTs or pasting them into an external debugger. Use a local decoder for diagnostics and remember that decoding does not validate a signature. Let the client acquire a fresh token according to MSAL’s documented cache behavior. In a production desktop application, attempt silent acquisition first and prompt interactively when needed.
Frequently asked questions
Is the API implementing an OAuth server?
Entra issues tokens. The API validates them and enforces its own authorization rules. This lab differs from the ASP.NET PB JWT tutorial, which explains bearer concepts separately.
Can a scheduled process use this scope?
A scheduled process usually uses an application identity and application permissions. Design and validate an app-role policy for that workload instead of treating a delegated scope as an application permission.
How should I secure a production client?
Use the identity platform’s supported client flow, restrict redirect URIs, apply least-privilege consent, and protect the token cache. For an ASP.NET Core web application, use the appropriate OIDC middleware and server-side configuration. This example uses a desktop public client.
The essential test is a negative one. A token from the right tenant can still be wrong for the resource or lack permission for the operation. Verify both failure cases before connecting the API to real order data.
Comments
Post a Comment