So far the SPA asks the API without an ID. This lesson gives it one: the user signs in to Entra ID, MSAL Angular fetches a token and attaches it to every request to /api, and the API checks scope and role. You know the sign-in and the registrations from K2 6 (signing in, app roles Buero and Leitung in 6.3); here only the browser's side is added. The part of the Kladde: core/auth and the API.

State of the checks. Not executed: every sign-in against Entra ID, every real token, the role check with a token; there was no tenant. Executed (Angular 22.2, @azure/msal-angular 6.3.1, @azure/msal-browser 5.29.0, Node 24.22.3): ng build exit code 0 (warning: the initial bundle is a good 24 kB over the budget of 500 kB), ng test --no-watch exit code 0 (test of the root component with stand-ins, plus the test from 0302). API probe (.NET 10.0.400, Microsoft.Identity.Web 4.18.0): dotnet build without a warning; without a token 401 with WWW-Authenticate: Bearer (curl). The MSAL statements come from lib/msal-angular/docs in the MSAL.js repository, from the .d.ts files and source of the installed package, and from Learn. A Learn guide for Angular with MSAL: open.

What happens in the browser

An SPA is a public client: its bundle sits as a file at the visitor's, and anyone can read it. This is how Learn puts it for the flow: a client_secret must not be used in an SPA, because it cannot be kept reliably on a web page; public clients redeem the code without a secret and without proof of a key. The flow:

  1. The SPA redirects the browser to Entra ID, with client_id, scope, a code_challenge (PKCE, S256) and redirect_uri.
  2. The user signs in there; Entra ID redirects back with a short-lived code (valid for about a minute, according to the page).
  3. MSAL exchanges the code for tokens and proves with the code_verifier that it is the same application that started the flow.
  4. The access token goes to the API as Authorization: Bearer ….

For SPAs, the redirect must be registered as type Single-page application; otherwise the exchange in the browser fails with CORS No 'Access-Control-Allow-Origin' header). The refresh token of such a redirect is valid for 24 hours according to the page, after which the sign-in runs through again. And: an access token is meant for the API. Learn advises treating it in the client as an opaque string and neither reading nor validating it. That determines the design below.