PKCE · Bearer · X-Api-Key

Auth og API-nøgler

To lag: log ind via Mercantec Auth (OAuth2 + PKCE) til jeres app, og kald OpgaverAPI med personlig X-Api-Key eller Bearer — uden at lække secrets i git.

Læringsmål

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:

  1. Generér code_verifier + code_challenge (S256) og state.
  2. Navigér brugeren til /oauth/authorize (fuld page navigation — ikke fetch).
  3. Callback med code — validér state, byt code mod tokens via POST /oauth/token.
  4. 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.

  1. Åbn kataloget — Mercantec Auth login hvis I ikke er inde.
  2. Opret nøgle — kopier én gang; gem i sessionStorage under jeres apps nøgle.
  3. Test med LoL-demo — Network skal vise X-Api-Key på request, ikke i query string.

Fail-scenarie: manglende nøgle

Vælg GET 401 og afspil — det matcher LoL-kald uden X-Api-Key.

KlientBlazor + nøgle?
OpgaverAPIValiderer header
# Vælg GET 401 og Afspil

sessionStorage vs localStorage

StorageLevetidTypisk brug
sessionStorageTab/sessionAccess token, API-nøgle hint, PKCE state midlertidigt
localStorageIndtil slettetUndgå til long-lived secrets — XSS-risiko
Memory onlyIndtil reloadMest 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.

  1. Generér code_verifier (43–128 tegn) og code_challenge = BASE64URL(SHA256(verifier)).
  2. Gem code_verifier og state i sessionStorage; send challenge + code_challenge_method=S256 i authorize URL.
  3. Navigér hele vinduet til https://auth.mercantec.tech/oauth/authorize — aldrig fetch/XHR til login.
  4. Callback med ?code=...&state=... — sammenlign state med gemt værdi; afvis ved mismatch (CSRF).
  5. POST /oauth/token med grant_type=authorization_code, code, code_verifier, client_id, redirect_uri.
  6. 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

16476 #1616476 #1716475 #16

Baseret på MAGS Blazor-wiki og Microsoft Learn — Blazor.