Step-by-Step Process & Entities


Entities Involved

1. End User (Browser)

2. Blazor WebAssembly Application (Frontend)

3. MSAL.js Library

4. Microsoft Entra ID (CIAM Tenant)

5. ApiService (Backend API)


High-Level Authentication Flows

Flow 1: User Login & Authentication (Steps 1-7)

┌─────────────────────────────────────────────────────────────────┐
│              LOGIN & AUTHENTICATION FLOW (Steps 1-7)            │
└─────────────────────────────────────────────────────────────────┘

    ┌──────────────┐
    │   End User   │
    │   Browser    │
    └──────┬───────┘
           │
           │ Step 1: Navigate to app
           │ (https://partners-dev.test-egzynergy.com/)
           ▼
    ┌──────────────────────┐
    │  Blazor WebAssembly  │
    │  (Partner Portal)    │
    └──────┬───────────────┘
           │
           │ Step 2: Check sessionStorage
           │ No valid token found
           │ → Redirect to login
           ▼
    ┌──────────────────────────────┐
    │  Microsoft Entra ID (CIAM)   │
    │  Login Page                  │
    └──────┬───────────────────────┘
           │
           │ Step 3: User enters
           │ email + password + MFA
           ▼
    ┌──────────────────────────────┐
    │  Microsoft Entra ID          │
    │  Validates Credentials       │
    └──────┬───────────────────────┘
           │
           │ ✅ Valid credentials
           │
           │ Step 4: Returns
           │ authorization code
           ▼
    ┌──────────────────────┐
    │  MSAL.js Library     │
    │  (in browser)        │
    └──────┬───────────────┘
           │
           │ Step 5: Exchange code
           │ for tokens (PKCE)
           ▼
    ┌──────────────────────────────┐
    │  Microsoft Entra ID          │
    │  Token Endpoint              │
    └──────┬───────────────────────┘
           │
           │ Returns 3 tokens:
           │ • Access Token (1h)
           │ • Refresh Token (90d)
           │ • ID Token (1h)
           ▼
    ┌──────────────────────┐
    │  Browser             │
    │  sessionStorage      │
    └──────┬───────────────┘
           │
           │ Step 6: Tokens stored
           │ (indexed by scope)
           │
           │ Step 7: Redirect back
           │ to original page
           ▼
    ┌────────────────────────────────┐
    │   ✅ USER AUTHENTICATED         │
    │   Ready to use the app         │
    └────────────────────────────────┘

Flow 2: API Calls with Token Validation (Steps 8-9)

┌─────────────────────────────────────────────────────────────────┐
│            API CALL & VALIDATION FLOW (Steps 8-9)               │
└─────────────────────────────────────────────────────────────────┘

    ┌────────────────────────────────┐
    │   User interacts with app      │
    │   (e.g., views overview page)  │
    └────────────┬───────────────────┘
                 │
                 │ Step 8: Blazor makes API request
                 ▼
    ┌──────────────────────────────────┐
    │  ApiAuthenticationHandler        │
    │  (HTTP Message Handler)          │
    └──────┬───────────────────────────┘
           │
           │ Requests token from MSAL.js
           │ Scope: api://4dad5d62.../ApiService_UserAccess
           ▼
    ┌──────────────────────┐
    │  MSAL.js Library     │
    │  sessionStorage      │
    └──────┬───────────────┘
           │
           │ Returns access token
           │ (or refreshes if expired)
           ▼
    ┌──────────────────────────────┐
    │  HTTP Request                │
    │  GET /api/v1/WorkOrder/...   │
    │  Authorization: Bearer eyJ... │
    └──────┬───────────────────────┘
           │
           │ Step 9: Request received
           ▼
    ┌──────────────────────────────────┐
    │  ApiService                      │
    │  JWT Authentication Middleware   │
    └──────┬───────────────────────────┘
           │
           │ Token Validation:
           │ ✓ Signature (Azure AD keys)
           │ ✓ Issuer (CIAM tenant)
           │ ✓ Audience (this API)
           │ ✓ Expiration (not expired)
           │ ✓ Groups (authorization)
           │
           ├─── ✅ Valid ────┐    ❌ Invalid ───┐
           │                 │                   │
           ▼                 ▼                   ▼
    ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
    │  Controller  │  │  200 OK      │  │  401/403     │
    │  Processes   │  │  Returns     │  │  Unauthorized│
    │  Request     │  │  Data        │  │  Forbidden   │
    └──────────────┘  └──────────────┘  └──────────────┘

┌───────────────────────────────────────────────────────────────┐
│  Step 10: TOKEN RENEWAL (automatic, happens in background)    │
│                                                                │
│  When access token expires (after 1 hour):                    │
│  1. MSAL.js detects expired token                             │
│  2. Uses refresh token to get new access token                │
│  3. Stores new access token in sessionStorage                 │
│  4. User continues working (no interruption)                  │
└───────────────────────────────────────────────────────────────┘

Step-by-Step Authentication Process


Step 1: User Visits Protected Page

What happens:

Who's involved:

Result:



Step 2: Redirect to Login

What happens:

Who's involved:

Redirect URL:

https://6073ce8b-73f3-4df4-9b80-5e40cdc6965f.ciamlogin.com/.../authorize
  ?client_id=84c38b43-12e4-4c26-8292-8910d79aa532
  &redirect_uri=https://partners.egzynergy.com/authentication/login-callback
  &response_type=code

Result:



Step 3: User Enters Credentials

What happens:

Who's involved:

Result:



Step 4: Authorization Code Issued

What happens:

Who's involved:

Redirect back:

https://partners.egzynergy.com/authentication/login-callback
  ?code=0.AXAA-very-long-code-here
  &state=random-state-value

Result:



Step 5: Exchange Code for Tokens

What happens:

Who's involved:

Request to Microsoft:

POST https://.../oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
code=0.AXAA-very-long-code-here
client_id=84c38b43-12e4-4c26-8292-8910d79aa532
redirect_uri=https://partners.egzynergy.com/authentication/login-callback
code_verifier=PKCE-verifier

Response from Microsoft:

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "refresh_token": "0.AXAA...",
  "id_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "expires_in": 3600
}

Result:



Step 6: Store Tokens

What happens:

Who's involved:

What's stored:

Result:



Step 7: Redirect to Original Page

What happens:

Who's involved:

Result:



Step 8: Making API Calls

What happens (every time app calls API):

  1. User interacts with the app (e.g., viewing the overview page)
  2. Blazor app makes HTTP request to ApiService
  3. ApiAuthenticationHandler intercepts the request
  4. Handler asks MSAL.js for access token
  5. MSAL.js returns token from sessionStorage
  6. Handler adds token to request header: Authorization: Bearer eyJ0eXAi...
  7. Request sent to ApiService

Who's involved:

Result:



Step 9: API Validates Token

What happens (on ApiService):

  1. ApiService receives request with Bearer token
  2. JWT middleware extracts token from header
  3. Checks token signature (validates it's from Microsoft)
  4. Checks token issuer (must be CIAM tenant)
  5. Checks token audience (must be for this API)
  6. Checks token expiration (must not be expired)
  7. Checks user's groups claim (for authorization)
  8. If all valid → Allow request
  9. If any invalid → Return 401 Unauthorized

Who's involved:

What's validated:

Result:



Step 10: Token Renewal (Automatic)

What happens (when access token expires):

  1. Access token expires after 1 hour
  2. Next API call triggers token renewal
  3. MSAL.js uses refresh token to get new access token
  4. New access token stored in sessionStorage
  5. API call proceeds with new token
  6. User doesn't notice anything (seamless)

Who's involved:

When user must log in again:



Token Details

Access Token (JWT)

Purpose: Proves user is authenticated and authorized for API calls Lifetime: 1 hour Contains:

Example (decoded):

{
  "iss": "https://6073ce8b-73f3-4df4-9b80-5e40cdc6965f.ciamlogin.com/.../v2.0",
  "aud": "api://4dad5d62-dc8c-4378-8bd0-ae736a4d73fe",
  "sub": "abc123...",
  "name": "John Doe",
  "email": "john.doe@contractor.com",
  "groups": ["ac6ec653-2ae3-457a-9302-d42429d83bee"],
  "exp": 1733754123
}

Refresh Token

Purpose: Get new access tokens without re-login Lifetime: 90 days Contains: Encrypted data (not readable) Note: Single-use (new refresh token issued with each renewal)

ID Token (JWT)

Purpose: User identity information for the frontend Lifetime: 1 hour Contains: Similar to access token but for frontend use Note: Not used for API authorization



Microsoft Graph API (Separate from Main Flow)

What Is It?

Microsoft Graph API is used for administrative and user management features - not for the main authentication flow described above.

When Is It Used?

1. Display User Information

2. Admin Features (Contractor/Grid Admins)

Separate Token Required

Graph API requires its own access token, different from the API token:

TokenAudienceUsed For
API Tokenapi://4dad5d62-dc8c-4378-8bd0-ae736a4d73feCalling ApiService endpoints
Graph Tokenhttps://graph.microsoft.comUser management via Graph API

How It Works

  1. During login (Step 3), user consents to Graph API permission:

  2. When Graph API is needed, app requests Graph token:

    var result = await _tokenProvider.RequestAccessToken(
        new AccessTokenRequestOptions
        {
            Scopes = new[] { "https://graph.microsoft.com/User.Read" }
        });
    
  3. MSAL returns separate token from sessionStorage (or gets new one)

  4. GraphServiceClient uses this token to call Microsoft Graph API

Example: Get Current User

What happens:

  1. Admin opens their profile page
  2. Page calls: await GraphUserService.GetCurrentUserAsync()
  3. GraphServiceClient requests Graph token from MSAL
  4. MSAL returns token with audience https://graph.microsoft.com
  5. Request sent to Microsoft Graph API with Bearer token
  6. Graph API returns user details (name, email)

Key Points

Files Involved

Service: Components/Authentication/AuthenticationService/GraphUserService.cs Configuration: Program.cs:104-142 Usage: Admin pages, NavBar, Profile page


Security Mechanisms

PKCE (Proof Key for Code Exchange)

What: Random secret generated by MSAL.js before login Why: Prevents authorization code theft How: Code can only be exchanged by app that started the flow

Token Signature Validation

What: Cryptographic signature on every JWT token Why: Proves token issued by Microsoft, not forged How: ApiService downloads Microsoft's public keys, validates signature

Token Expiration

What: Every token has expiration timestamp Why: Limits damage if token stolen How: ApiService rejects expired tokens automatically

sessionStorage (Not localStorage)

What: Browser storage that clears when tab closes Why: Reduces risk if user leaves computer unlocked How: MSAL.js configured to use sessionStorage