Skip to content

Architecture

Overview

The Intune Assignments Manager is a client-side single-page application built with SvelteKit 2. There is no server backend — all authentication and API communication happens directly in the browser. The app is deployed to Cloudflare Pages as static assets.

The core data flow is:

  1. User authenticates via Microsoft Entra ID using MSAL.js (OAuth2 PKCE popup flow)
  2. The app acquires access tokens and calls the Microsoft Graph API directly from the browser
  3. Graph API responses are validated with Zod schemas and rendered in the UI

System Flow

flowchart LR
    Browser["Browser (SvelteKit SPA)"]
    MSAL["MSAL.js"]
    EntraID["Azure Entra ID"]
    GraphClient["Graph Client"]
    GraphAPI["Microsoft Graph API"]
    Intune["Intune Backend"]

    Browser --> MSAL
    MSAL -->|OAuth2 PKCE| EntraID
    EntraID -->|Access Token| MSAL
    MSAL -->|Token| Browser
    Browser --> GraphClient
    GraphClient -->|Bearer Token| GraphAPI
    GraphAPI --> Intune
    Intune -->|Apps, Profiles, Groups| GraphAPI
    GraphAPI -->|JSON| GraphClient
    GraphClient -->|Validated Data| Browser

Authentication Flow

sequenceDiagram
    participant User
    participant App as SvelteKit App
    participant MSAL as MSAL.js
    participant Popup as Auth Popup
    participant Entra as Azure Entra ID

    User->>App: Click "Sign In"
    App->>MSAL: Lazy-import & initialize
    MSAL->>Popup: Open loginPopup()
    Popup->>Entra: Redirect to login.microsoftonline.com
    User->>Entra: Enter credentials & consent
    Entra->>Popup: Return auth code
    Popup->>MSAL: Exchange code for tokens (PKCE)
    MSAL->>App: Return TokenResult (accessToken, scopes, account)
    App->>App: Store account in auth rune state
    App->>App: Merge granted scopes

    Note over App,MSAL: Subsequent API calls
    App->>MSAL: acquireTokenSilent()
    alt Token cached & valid
        MSAL->>App: Return cached token
    else Interaction required
        MSAL->>Popup: acquireTokenPopup()
        Popup->>App: Return fresh token
    end

MSAL is lazy-imported on first auth action to maintain SSR-safety (guarded by SvelteKit's browser check). See Authentication for full details.

Routing

SvelteKit file-based routing provides the following pages:

RoutePageDescription
/DashboardOverview cards with app/profile counts, recent audit activity
/appsApp ListBrowse all Intune mobile apps with search and filtering
/apps/[id]App DetailView a single app's properties and current assignments
/profilesProfile ListBrowse all configuration policies with search and filtering
/profiles/[id]Profile DetailView a single profile's properties and current assignments
/assignAssignment Wizard5-step bulk assignment workflow
/auditAudit LogBrowse Intune audit events
/statusDeployment StatusApp install status via Reports API
/settingsSettingsPermission management and consent controls

The root layout (src/routes/+layout.svelte) wraps all pages with authentication guards, navigation, and theme support.

State Management

The app uses pure Svelte 5 runes for all state management — no external state library (no Redux, no Svelte 4 stores).

Stores live in src/lib/stores/ and export reactive state via $state and $derived:

StoreFilePurpose
Authauth.svelte.tsCurrent account, authentication state, granted scopes
Notificationsnotifications.svelte.tsToast notification queue (success, error, info)
Themetheme.svelte.tsDark/light mode preference
Permissionspermissions.svelte.tsGranted scopes, consent error tracking
Shortcutsshortcuts.svelte.tsKeyboard shortcut registry
Command Palettecommand-palette.svelte.tsCommand palette open/close state
Dashboard Cachedashboard-cache.svelte.tsCached app/profile counts and recent activity
Filter Cachefilter-cache.tsCached assignment filter definitions
Group Cachegroup-cache.tsCached group name resolutions
Graph Clientgraph.tsSingleton graph client instance

Three-Tier Type System

The codebase uses a layered approach to type safety:

flowchart TD
    API["Microsoft Graph API Response (JSON)"]
    ZOD["Zod Schemas (src/lib/types/schemas.ts)"]
    TS["TypeScript Interfaces (src/lib/types/graph.ts)"]
    BIZ["Business Logic Types (src/lib/types/wizard.ts)"]

    API -->|Runtime validation| ZOD
    ZOD -->|Parse & validate| TS
    TS -->|Compose into| BIZ
  1. TypeScript interfaces (src/lib/types/graph.ts) define the shape of Graph API response objects — MobileApp, ConfigurationPolicy, MobileAppAssignment, etc.

  2. Zod schemas (src/lib/types/schemas.ts) provide runtime validation when parsing API responses. The fetchAll method uses schemas to validate each page of results.

  3. Business logic types (src/lib/types/wizard.ts) compose Graph types into higher-level abstractions — AssignableItem, GroupTarget, ConflictChoice, AssignmentResult, etc.

Additional type files:

FilePurpose
src/lib/types/diff.tsAssignment diff types for comparing before/after states
src/lib/types/status.tsStatus and reports types
src/lib/types/status-schemas.tsZod schemas for Reports API responses

Graph API Client Architecture

The Graph client is a factory function that returns three methods:

flowchart TD
    Factory["createGraphClient(getAccessToken)"]
    Request["request<T>(path, options?)"]
    FetchAll["fetchAll<T>(path, options?)"]
    Batch["batch(requests[], options?)"]
    Retry["Retry Logic (429 backoff, 401 re-auth)"]
    Pagination["Pagination (@odata.nextLink)"]
    Chunking["Auto-chunk at 20 requests"]

    Factory --> Request
    Factory --> FetchAll
    Factory --> Batch
    Request --> Retry
    FetchAll --> Pagination
    Batch --> Chunking
    Chunking --> Request
  • request<T>() — Single HTTP request with automatic retry: exponential backoff on 429 (rate limit), one re-auth attempt on 401
  • fetchAll<T>() — Paginated fetch that follows @odata.nextLink up to a configurable page limit (default 50)
  • batch() — Sends requests to the /$batch endpoint, auto-chunks at 20 requests per batch, retries individual 429 responses

All three methods default to the beta Graph API endpoint (https://graph.microsoft.com/beta). Pass version: 'v1.0' to use the stable endpoint.

See Graph API Client for the full deep dive.

Bulk Assignment Execution Flow

The bulk assignment wizard uses a three-phase execution model in src/lib/graph/execute.ts:

flowchart LR
    subgraph "Phase 1: Fetch"
        F1["Batch GET current assignments"]
        F2["Fallback: individual fetch if paginated"]
    end

    subgraph "Phase 2: Merge"
        M1["Combine existing + new assignments"]
        M2["Detect conflicts (same group, different settings)"]
        M3["Apply conflict resolutions (update or skip)"]
    end

    subgraph "Phase 3: Apply"
        A1["Batch POST to /assign endpoints"]
        A2["Retry 5xx errors (2x, exponential backoff)"]
    end

    F1 --> F2
    F2 --> M1
    M1 --> M2
    M2 --> M3
    M3 --> A1
    A1 --> A2

Replace semantics

The Graph API assign endpoint replaces all assignments for an item. Existing assignments must always be fetched and merged first to avoid accidentally removing assignments that were not part of the current operation.

Merge Logic

The merge module (src/lib/graph/merge.ts) uses a target-key approach:

  1. Index existing assignments by a deterministic key (e.g., group::{groupId}, exclusion::{groupId}, allDevices, allUsers)
  2. For each new assignment, check if that target key already exists
  3. If a conflict is found, apply the user's resolution choice: update (overwrite) or skip (keep existing)
  4. Return the full merged list to POST back to the API

Caching Strategy

DataStorageTTLInvalidation
Dashboard counts (apps, profiles, assigned)localStorage15 minutesManual refresh, list page loads
Recent audit activitylocalStorage15 minutesBundled with dashboard cache
Assignment filter definitionsIn-memory (module scope)Session lifetimeclearFilterCache()
Group name resolutionsIn-memory (Map)Session lifetimeclearGroupCache()
MSAL tokenslocalStorage (MSAL-managed)Token lifetimeMSAL handles refresh automatically
Granted scopeslocalStorageUntil sign-outCleared on logout