A production-ready Next.js React SPA template for Microsoft Entra ID authentication using MSAL and Microsoft Graph.
Live demo: https://spa.identityworkbench.com/
Built for: real-world identity-enabled applications, not just samples
This template provides a clean, working foundation to:
- Sign users in with Microsoft Entra ID
- Acquire delegated access tokens using MSAL
- Query and render Microsoft Graph data
- Build responsive UI using modern React patterns (hooks, SWR, Fluent UI)
It reflects how identity-enabled applications are actually built in practice - not just how they are documented.
If this project saves you time or helps you get started faster, consider giving it a star.
https://spa.identityworkbench.com/
www.linkedin.com/in/chris-dymond
This template brings together patterns, lessons, and real-world experience working with MSAL, React, and Microsoft Graph into a single, practical starting point.
Instead of piecing together multiple samples and docs, the goal is to provide a clean foundation that just works.
If it saves you time or helps you avoid common pitfalls, it’s done its job.
This is a browser-based SPA. Anything shipped to the client should be treated as public.
Do not include:
- Secrets (client secrets, API keys, certificates)
- Sensitive logic or decision-making
- Server-side enforcement of access controls
This template uses MSAL with Microsoft Entra ID (Authorization Code Flow with PKCE), which is appropriate for public clients.
If your application requires sensitive or security-critical operations, these should be handled by a backend or API layer where access control and enforcement can be trusted.
- Engineers building Microsoft Entra ID-integrated SPAs
- Teams needing a quickstart for MSAL React + Microsoft Graph
- Anyone tired of stitching together multiple identity samples
- Why this repo
- Feature highlights
- Prerequisites
- Setup
- Available scripts
- Project layout
- Authentication & Graph architecture
- Customization playbook
- Troubleshooting
- Helpful links
- Pre-configured identity plumbing – MSAL is pre-configured with redirect handling, active-account management, and silent SSO so you can focus on product work.
- Graph-ready data layer with typed hooks and resilient fetching – Typed hooks and a resilient fetcher abstract access tokens, pagination, and transient failures.
- Fluent UI-based application shell – Fluent UI navigation, responsive layout, loading states, and reusable error dialogs already wired up.
- ✅ Next.js 16 + React 19 App Router with client components where needed and a global provider stack in
app/layout.tsx. - ✅ MSAL React 5 wrapper (
quickstart/providers/msal/MsalClientProvider.tsx) that initializes once, handles redirect promises, and keeps the active account in sync. - ✅ Microsoft Graph helpers
quickstart/providers/msgraph/msGraphFetcher.tsinjects access tokens, retries pagination via@odata.nextLink, and now adds exponential backoff + friendly throttling hints for 429/5xx responses.quickstart/hooks/useGraphData.tswraps SWR for caching, revalidation, and typed responses.quickstart/hooks/useScopes.tsdetects missing consent and triggers interactive flows.
- ✅ Fluent UI 9 design system with a sticky app shell (
quickstart/ui/layouts/ClientShell.tsx), adaptive nav (quickstart/ui/navigation/NavMenu.tsx), and sign-in/out controls fed by MSAL account data. - ✅ Route protection baked in – the
app/(authenticated)/layout.tsxgroup wraps every protected page withAuthenticatedClientShell, which usesMsalAuthenticationTemplateto require sign-in (and display helpful loading/error states) even on deep links. - ✅ Sample Graph pages
app/(authenticated)/profile/page.tsxrenders/medata with incremental consent prompts.app/(authenticated)/organization/page.tsxcalls/organizationto show tenant metadata (requiresOrganization.Read.All).
- ✅ Utility components such as the global
ErrorDialogProviderandLoadingScreenkeep UX consistent during auth flows.
- Node.js 18.18+ or 20+ (matches Next.js 16 requirements) and npm 9+.
- A Microsoft Entra ID (Azure AD) tenant with permissions to create app registrations.
- An account with permission to grant delegated Microsoft Graph scopes (User.Read, Organization.Read.All, etc.).
npm install- Visit Azure Portal › Entra ID › App registrations.
- New registration ➜ name it (e.g.,
Next.js MSAL Graph SPA). - Supported account types: choose Single tenant (recommended for demos) or Multi-tenant depending on your scenario.
- Redirect URI (type Single-page application):
http://localhost:3000. - After creating the app:
- Copy the Application (client) ID.
- Under Authentication, add your redirect URIs (e.g.,
http://localhost:3000https://yourdomain.com). - Under API permissions, add delegated Microsoft Graph scopes:
Permission Reason User.ReadLoad profile data via /meAny extra scopes your solution needs Add them now or later
Duplicate .env.example as .env.local (or any .env*.local) and adjust values:
NEXT_PUBLIC_APP_NAME="Next.js MSAL React SPA Quickstart"
NEXT_PUBLIC_TENANT_ID="<directory (tenant) ID or 'common' if a multi-tenant app>"
NEXT_PUBLIC_CLIENT_ID="<application (client) ID>"
NEXT_PUBLIC_MSAL_REDIRECT_URI="http://localhost:3000"
NEXT_PUBLIC_MSAL_SCOPES="User.Read"Notes:
- The app now fails fast if
NEXT_PUBLIC_TENANT_ID,NEXT_PUBLIC_CLIENT_ID, orNEXT_PUBLIC_MSAL_SCOPESare missing—restartnpm run devwhenever you edit.env.localso Next.js reloads them. - All variables are read in
config/appConfig.tsand consumed across MSAL + Graph helpers. NEXT_PUBLIC_MSAL_SCOPESis a comma-separated list; align it with the permissions you granted. The profile page checks these scopes viauseScopesbefore rendering data.- For multi-environment deployments, create
.env.development.local/.env.production.localor workspace secrets.
npm run devVisit http://localhost:3000 and select Sign in. Once consented, explore Profile and Organization from the sidebar.
| Script | Description |
|---|---|
npm run dev |
Starts Next.js with hot reload and client-side MSAL initialization logs. |
npm run build |
Production build (generates .next/). Useful for CI/CD validation. |
npm run start |
Serves the production build; ensure env vars are set in your hosting platform. |
npm run lint |
Runs ESLint (Next.js config) to catch common mistakes. |
app/
├─ layout.tsx # Global providers + styles
├─ page.tsx # Quickstart landing page
└─ (authenticated)/ # Routes that require MSAL auth
├─ profile/page.tsx # /me sample
└─ organization/page.tsx # /organization sample
config/
├─ appConfig.ts # Reads NEXT_PUBLIC_* env vars
└─ NavItems.tsx # Sidebar + mobile nav definitions
quickstart/
├─ providers/msal/ # MSAL config, helpers, navigation client
├─ providers/msgraph/ # Graph endpoints + fetcher utilities
├─ hooks/ # Reusable React hooks (Graph data, scopes)
├─ ui/layouts/ClientShell.tsx # Fluent UI shell with navbar + sidebar
├─ ui/navigation/ # Nav components & sign-in/out menu
└─ ui/common/ # Error dialog + loading spinner
- AppProviders wraps the tree with
MsalClientProvider, Fluent UI theme, and theErrorDialogProviderso every route can surface failures. - MsalClientProvider ensures
PublicClientApplicationinitializes once per browser, handleshandleRedirectPromise, sets the active account, and tries silent SSO if possible. - ClientShell renders a toolbar (
NavBar) + sidebar (NavMenu).NavMenufilters routes that require auth usinguseIsAuthenticatedso anonymous users only see what they can access. - Sign-in/out buttons reuse
handleSignIn/handleSignOut(redirect flows) and display profile photos fetched from/me/photos/48x48/$valuevia SWR. - Graph calls
useGraphData(resource)→msGraphFetcher→ fetch withAuthorization: Bearer <token>.msGraphFetcherinjects tokens, optionalConsistencyLevel, paginates automatically, and throws descriptive errors surfaced by cards/dialogs.
- Consent flow:
useScopeschecks delegated scopes silently, shows descriptive CTA cards, and can force consent when needed.
- Rename the app: change
NEXT_PUBLIC_APP_NAMEor override the fallback string inconfig/appConfig.ts. - Add pages: duplicate the pattern inside
app/(authenticated)/and register new routes inconfig/NavItems.tsxwithrequiresAuthflags. - Call additional Graph endpoints: extend
msGraphEndpointsand reuseuseGraphDatafor SWR-powered UI updates. - Handle errors centrally: import
useErrorDialog()anywhere inside the provider tree to show blocking issues (token failures, Graph throttling, etc.). - Style it: adjust Fluent UI tokens in
quickstart/styling/fluentui/styles.tsor wrapFluentProviderwith a custom theme. - Charts & data viz: Recharts 3 is already installed—perfect for rendering usage reports with Graph analytics data.
- "No active account found" – Sign in first;
msalInstancecannot fetch tokens without an account. This is thrown bygetMsGraphAccessToken. interaction_requirederrors – The user must consent to new scopes. The Profile page surfaces a "Grant profile access" card that callsrequestConsent().- Organization screen empty – Ensure an admin granted
Organization.Read.All. Without it, Graph returns HTTP 403. - Redirect URI mismatch – Update the SPA redirect in Azure Portal to exactly match
NEXT_PUBLIC_MSAL_REDIRECT_URI(including protocol + port).
- MSAL React documentation
- Microsoft Graph delegated permissions
- Fluent UI React Components
- Next.js deployment guide
Need more? File an issue or open a discussion—feedback on the template is welcome!
