Læringsmål
- Skitsere authorization code + PKCE flow mod
auth.mercantec.tech. - Sætte
Authorization: Bearerpå API-kald når backend kræver JWT. - Oprette og bruge
X-Api-Keytil beskyttede OpgaverAPI-routes (fx LoL). - Vælge sessionStorage frem for localStorage til tokens i SPA.
- Forstå forskellen på «log ud af appen» vs global Auth-signout.
Hvornår bruger I hvad? Mercantec Auth når jeres app skal kende brugeren (sub, roller). X-Api-Key når OpgaverAPI skal kvota/kende eleven — uafhængigt af OAuth. Mange projekter bruger begge: login på katalog + nøgle til LoL.
Tjek dig selv før du går videre
- Du kan liste PKCE-trin (verifier, challenge, state, token).
- App-logout rydder kun lokale tokens — ikke global Auth-signout.
- Du ved hvornår X-Api-Key vs Bearer bruges.
Mercantec Auth — overblik
Mercantec Auth er den centrale identitetsplatform. Jeres Blazor SPA er en public client: ingen client secret i browseren. Flow:
- Generér
code_verifier+code_challenge(S256) ogstate. - Navigér brugeren til
/oauth/authorize(fuld page navigation — ikke fetch). - Callback med
code— validérstate, byt code mod tokens via POST/oauth/token. - Gem access/refresh i sessionStorage; send Bearer på API-kald.
Autoritativ konfiguration: mercantec-auth.json · Audience typisk mercantec-apps.
// Eksempel: HttpClient med Bearer fra session (pseudo)
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
Faldgrube: Kald aldrig https://auth.mercantec.tech/signout fra jeres almindelige «Log ud»-knap — det logger brugeren ud af hele Auth-sessionen. App-logout = ryd tokens lokalt og gå til login-side.
Hvornår fuld page navigation til authorize? Altid ved login-start — browser skal vise Auth UI og sætte cookies korrekt. Fetch til /oauth/authorize fejler eller blokeres; PKCE + state kræver redirect tilbage til jeres redirect_uri.
X-Api-Key mod OpgaverAPI
Åbne endpoints (fx mange Pokémon-routes) kræver ikke nøgle. Beskyttede routes (fx League of Legends) returnerer 401 uden header. Opret nøgle i API-kataloget når I er logget ind — gem den i sessionStorage, send som header:
request.Headers.Add("X-Api-Key", apiKey);
var champs = await http.GetFromJsonAsync<ChampionDto[]>("/LeagueOfLegends/Champions");
Commit aldrig nøgler. Brug user secrets lokalt og env i Dokploy til server-delen hvis I proxy’er.
- Åbn kataloget — Mercantec Auth login hvis I ikke er inde.
- Opret nøgle — kopier én gang; gem i sessionStorage under jeres apps nøgle.
- Test med LoL-demo — Network skal vise
X-Api-Keypå request, ikke i query string.
Fail-scenarie: manglende nøgle
Vælg GET 401 og afspil — det matcher LoL-kald uden X-Api-Key.
# Vælg GET 401 og Afspil
sessionStorage vs localStorage
| Storage | Levetid | Typisk brug |
|---|---|---|
| sessionStorage | Tab/session | Access token, API-nøgle hint, PKCE state midlertidigt |
| localStorage | Indtil slettet | Undgå til long-lived secrets — XSS-risiko |
| Memory only | Indtil reload | Mest sikkert for access token hvis I accepterer re-login ved refresh |
PKCE code_verifier og state hører i sessionStorage indtil callback er valideret — derefter ryd midlertidige værdier.
Hvornår localStorage? Næsten aldrig til access/refresh tokens i Mercantec SPA-retningslinjer — længere eksponering ved XSS. Præferencer uden sikkerhed (tema, sidste søgning) kan overvejes, men ikke nøgler.
Refresh og 401
Wrapper om HttpClient: ved 401 forsøg ét token refresh, retry request — ellers redirect til login. Roterende refresh tokens: gem altid det nye svar fra token-endpoint.
// Pseudokode — AuthHttpHandler
protected override async Task<HttpResponseMessage> SendAsync(request, ct)
{
var response = await base.SendAsync(request, ct);
if (response.StatusCode != HttpStatusCode.Unauthorized) return response;
var newTokens = await _auth.RefreshAsync(ct); // POST /oauth/token, grant_type=refresh_token
if (newTokens is null) { _nav.NavigateTo("/login"); return response; }
_session.SaveTokens(newTokens); // overskriv BÅDE access og refresh
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newTokens.AccessToken);
return await base.SendAsync(request, ct); // ét retry — undgå uendelig loop
}
Token rotation: Hvis I gemmer gammel refresh efter refresh-svar, næste login fejler med «invalid_grant». OpgaverAPI 401 med gyldig Bearer betyder ofte udløbet token — skeln fra manglende X-Api-Key.
Elev-fejl → symptom → fix: Refresh i loop ved hver GET — symptom: rate limit / logout. Fix: kun én retry; ved gentagen 401 → lokal logout.
Quiz 1
Hvilken header bruger I til personlig OpgaverAPI-nøgle mod LoL?
Quiz 2
Hvorfor foretrækkes sessionStorage til SPA-tokens i Mercantec-retningslinjer?
PKCE trin for trin
Gå PKCE igennem
Tryk Afspil — eller hop mellem trin. Det er det flow I skal kunne tegne til censor.
Hvorfor det betyder noget: Jeres Blazor-app er en public OAuth-klient uden hemmelighed i browseren. PKCE binder authorization code til den session der startede login — så en aflyttet code ikke kan byttes uden code_verifier.
- Generér
code_verifier(43–128 tegn) ogcode_challenge = BASE64URL(SHA256(verifier)). - Gem
code_verifierogstatei sessionStorage; send challenge +code_challenge_method=S256i authorize URL. - Navigér hele vinduet til
https://auth.mercantec.tech/oauth/authorize— aldrig fetch/XHR til login. - Callback med
?code=...&state=...— sammenlignstatemed gemt værdi; afvis ved mismatch (CSRF). - POST
/oauth/tokenmedgrant_type=authorization_code,code,code_verifier,client_id,redirect_uri. - Gem access/refresh token i sessionStorage; slet PKCE-midlertidige nøgler.
// Pseudokode — Login.razor / AuthService
var verifier = Pkce.GenerateVerifier();
var challenge = Pkce.S256(verifier);
session.Set("pkce_verifier", verifier);
session.Set("oauth_state", state);
Nav.NavigateTo($"{auth}/oauth/authorize?client_id=...&code_challenge={challenge}&...", forceLoad: true);
Hvornår PKCE igen? Ved hvert nyt login-forsøg — generér ny verifier/challenge/state; genbrug ikke gamle værdier fra en afbrudt session.
Lokal logout vs global signout
Lokal logout (standard «Log ud» i jeres app): ryd kun tokens og API-nøgle-hints i sessionStorage, nulstil in-memory auth-state, gå til /login. Brugeren forbliver logget ind i andre Mercantec-apps.
Global signout (auth.mercantec.tech/signout): kun når brugeren eksplicit vil ud af hele Auth-platformen — ikke jeres default-knap.
Hvornår lokal logout? Standard «Log ud» i navbar, før demo til censor, eller når eleven deler PC — ryd sessionStorage for tokens og api_key, så næste bruger ikke arver LoL-kvota.
void LogoutLocal(IJSRuntime js, NavigationManager nav)
{
js.InvokeVoid("eval", @"
['access_token','refresh_token','pkce_verifier','oauth_state','api_key']
.forEach(k => sessionStorage.removeItem(k));");
nav.NavigateTo("/login", forceLoad: true);
}
Sådan fejler det i praksis: «Log ud» kalder global signout — eleven undrer sig over at Mercantec Mail også er logget ud. Eller: tokens i localStorage overlever ugevis og stjæles ved XSS.
Mini-opgave
Skriv PKCE-pseudokode: verifier, S256 challenge, state i sessionStorage, fuld navigation authorize, validate state, token POST.
Quiz 3
Log ud i jeres app?
Læs hos Microsoft
Prøv med API
- Log ind på kataloget, opret nøgle, kør LoL-demo — sammenlign med 401 i animationen ovenfor.
- Læs AI-prompt for Auth-integration før I implementerer PKCE i eget projekt.
Baseret på MAGS Blazor-wiki og Microsoft Learn — Blazor.