Web App Needs User Login? Use Authorization Code Flow

Published on:

In enterprise systems, web applications often need to sign in users and call APIs on their behalf.

This pattern is used when:

  • a user is present
  • the application runs on a trusted server
  • authentication is handled by Microsoft Entra ID
  • the app needs delegated access to an API

In this example:

  • CloudTrips Web App is the client application
  • CloudTrips API is the resource application
  • Employee is the signed-in user

Authentication is done with OAuth 2.0 Authorization Code Flow. Authorization is controlled with delegated permissions.

Architecture

Employee -> CloudTrips Web App -> Microsoft Entra ID -> CloudTrips API

The browser signs in through Microsoft Entra ID.

The web app receives an authorization code.

The web app exchanges that code for tokens from the server side.

Demo Apps

This trip can be demonstrated with the local sample apps in:

examples/auth-code-flow

The demo contains:

  • CloudTrips-Web on http://localhost:5001
  • CloudTrips-API on http://localhost:5002

Use the demo redirect URI:

http://localhost:5001/signin-oidc

For production, use HTTPS and the real application URL.

Create CloudTrips API App

First, create the resource application.

Go to:

Entra ID > App registrations > New registration

Create an application:

Name: CloudTrips-API

This app registration represents the API that will receive and validate access tokens.

Microsoft Entra app registration creation page with CloudTrips API entered as the application name

Expose API

Inside CloudTrips-API, open:

Expose an API

Set the Application ID URI:

api://<cloudtrips-api-application-id>

This identifier becomes the audience for access tokens issued to the CloudTrips API.

CloudTrips API App registration on the Expose an API page with the Application ID URI configured

Create Delegated Scope

Inside CloudTrips-API, add a scope:

Expose an API > Add a scope

Example:

Scope name: Trips.Read
Who can consent: Admins and users
Admin consent display name: Read CloudTrips trips
User consent display name: Read your CloudTrips trips
State: Enabled

This defines user-delegated authorization.

The CloudTrips API can later check whether the incoming token contains the required scope.

Add a scope form for CloudTrips API showing Trips.Read and consent settings

Create CloudTrips Web App

Now create the client application.

Go to:

Entra ID > App registrations > New registration

Create an application:

Name: CloudTrips-Web
Supported account types: Single tenant
Redirect URI platform: Web
Redirect URI: http://localhost:5001/signin-oidc

The redirect URI is where Microsoft Entra ID sends the browser after the user signs in.

For production, use the real application URL instead of localhost.

Microsoft Entra app registration creation page with CloudTrips Web and a web redirect URI

Create Client Secret

Inside CloudTrips-Web, create a client secret:

Certificates & secrets > New client secret

Store the secret value securely.

The web app uses this secret when it exchanges the authorization code for tokens.

In real projects, prefer certificate-based credentials for production web apps where possible. If a client secret is used, rotate it regularly and never store it in source control.

CloudTrips Web Certificates and secrets page showing a created client secret

Add API Permission

Go to:

App registrations > CloudTrips-Web > API permissions

Then select:

Add a permission

Choose:

My APIs > CloudTrips-API > Delegated permissions > Trips.Read

This allows the CloudTrips Web App to request an access token for the CloudTrips API on behalf of the signed-in user.

CloudTrips Web API permissions page showing the CloudTrips API delegated Trips.Read permission selected

If your tenant requires administrator approval, click:

Grant admin consent

CloudTrips Web API permissions page after admin consent is granted

Authorization Code Flow at Runtime

At runtime, the browser is redirected to Microsoft Entra ID:

GET https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize

Request parameters:

client_id=CLOUDTRIPS_WEB_CLIENT_ID
response_type=code
redirect_uri=http://localhost:5001/signin-oidc
response_mode=form_post
scope=openid profile offline_access api://<cloudtrips-api-application-id>/Trips.Read
state=<random-state-value>

The user signs in and completes any required Conditional Access or MFA challenge.

Microsoft Entra sign-in page for the employee account

Microsoft Entra ID then redirects the browser back to:

http://localhost:5001/signin-oidc

The response contains an authorization code.

Browser developer tools showing the signin-oidc callback with an authorization code

Exchange Code for Tokens

The web app sends the authorization code to Microsoft Entra ID from the server side:

POST https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token

Request body:

client_id=CLOUDTRIPS_WEB_CLIENT_ID
client_secret=SECRET
grant_type=authorization_code
code=AUTHORIZATION_CODE
redirect_uri=http://localhost:5001/signin-oidc
scope=openid profile offline_access api://<cloudtrips-api-application-id>/Trips.Read

The client secret is sent only from the server.

It must never be exposed in browser JavaScript.

Application log showing the authorization code token request to Microsoft Entra

Token Result

The token response usually contains:

  • ID token for the signed-in user
  • access token for the CloudTrips API
  • refresh token if offline_access was requested and allowed

The ID token proves who signed in to the web app.

The access token is sent to the CloudTrips API.

Application log showing a successful token response with ID token and access token returned

Access Token Claims

The access token contains:

  • audience: CloudTrips API
  • scope: Trips.Read
  • user identity
  • tenant ID
  • token issuer

Important claims:

Claim Meaning
aud API that should accept the token
scp Delegated permissions granted to the client app
sub Stable user identifier for this app
tid Tenant ID
iss Token issuer

Decoded access token showing audience, delegated scope, tenant, subject, and issuer claims

The CloudTrips API should validate the token before accepting the request.

At minimum, validate:

  • signature
  • issuer
  • audience
  • expiration
  • required scope

Delegated Access

Authorization Code Flow creates a delegated token.

That means:

  • a user signed in
  • the web app acts on behalf of that user
  • the API receives user context
  • permissions appear in the scp claim

This is different from Client Credentials Flow.

Client Credentials Flow creates an application token with no user identity.

Extra: Add App Role Through a Group

Scopes answer this question:

What is the client app allowed to do on behalf of the user?

App roles answer a different question:

What role does this signed-in user have inside the application?

For example, CloudTrips may need an application role such as:

Trips.Approver

This role can be assigned to a Microsoft Entra group. When a user from that group signs in and receives a token for the CloudTrips API, the access token can contain a roles claim.

Create a User App Role

Inside CloudTrips-API, open:

App roles > Create app role

Example:

Display name: Trips Approver
Allowed member types: Users/Groups
Value: Trips.Approver
Description: Can approve CloudTrips travel requests
Do you want to enable this app role: Yes

This role belongs to the resource application, because the CloudTrips API is the app that will read and enforce the role.

CloudTrips API App roles page showing Trips Approver with the Trips.Approver value

Assign a Group to the App Role

Open the Enterprise Application for the API:

Entra ID > Enterprise applications > CloudTrips-API

Then go to:

Users and groups > Add user/group

Select a group:

GRP-CloudTrips-Approvers

Select the role:

Trips Approver

Now every user in that group receives the application role when getting a token for the CloudTrips API.

The user may need to sign out and sign in again before the new role appears in the token.

CloudTrips API Enterprise Application Users and groups page showing the approvers group assigned to the Trips Approver role

Role Claim Result

After the user signs in again, decode the access token.

The token can now contain both:

{
  "scp": "Trips.Read",
  "roles": [
    "Trips.Approver"
  ]
}

Decoded access token showing both Trips.Read scope and Trips.Approver role

The CloudTrips API can then check both values:

  • scp confirms delegated permission to call the API
  • roles confirms the user’s application role

For example:

Allow reading trips if scp contains Trips.Read
Allow approving trips if roles contains Trips.Approver

This is usually cleaner than checking raw group IDs in the API. Group IDs are tenant-specific, harder to read, and large group memberships can cause token overage behavior.

Enterprise Note

Real enterprise environments usually have multiple stages:

  • DEV
  • TEST or STAGE
  • PROD

If these environments use different application URLs or different API deployments, create separate App Registrations per environment.

Example:

CloudTrips-Web-DEV
CloudTrips-Web-TEST
CloudTrips-Web-PROD

CloudTrips-API-DEV
CloudTrips-API-TEST
CloudTrips-API-PROD

This provides:

  • isolated redirect URIs
  • separated client credentials
  • safer production consent boundaries
  • clearer token audiences
  • cleaner auditing and troubleshooting

Key Comparison

Flow User Present Token Type Common Use
Authorization Code Yes Delegated Server-side web app
Client Credentials No Application Background system integration
Managed Identity No Application Azure-hosted workload