Én side, hele historien: hvorfor central login, hvordan OAuth 2.0 + PKCE virker, hvad OIDC lægger ovenpå, og hvordan tokens lever og dør. Skrevet til undervisning — følg diagrammerne oppefra og ned.
Uden en central auth-server skal hver app selv opbevare passwords, selv bygge “glemt adgangskode”, selv håndtere Google/Microsoft-login — og brugeren får én konto pr. app. Med en central server logger brugeren ind ét sted, og alle apps får det samme stabile bruger-ID.
sub (bruger-GUID) i alle appsOAuth 2.0 er en protokol for delegeret adgang: appen får et token på brugerens vegne uden nogensinde at se brugerens password. Denne platform bruger kun det sikreste browser-flow: authorization code + PKCE (S256). Fire parter er i spil:
En browser-app kan ikke holde på en hemmelighed — alt i JavaScript kan læses af brugeren (og angribere). PKCE (Proof Key for Code Exchange) erstatter den faste hemmelighed med en engangs-hemmelighed pr. login:
code_verifier: tilfældig streng, genereres i browseren, forlader den aldrig før token-kaldet.code_challenge = BASE64URL(SHA256(verifier)): sendes åbent i authorize-kaldet — kan ikke regnes baglæns.code undervejs (fx via logs eller redirect), kan de ikke bytte den til tokens uden verifieren.Skriv i verifier-feltet og se challengen ændre sig med det samme — det er ægte SHA-256 i din browser (samme kode som login-knappen på forsiden bruger). Prøv at ændre ét enkelt tegn.
—
Bemærk: challengen ligner volapyk uanset input, og en lille ændring i verifieren giver en helt anden challenge. Der findes ingen vej tilbage fra challenge til verifier — det er hele pointen.
Sådan ser den URL ud som din app sender brugeren til. Redigér felterne og se URL'en opdatere sig — challengen kommer live fra legepladsen ovenfor.
—
Farverne viser de enkelte parametre. response_type=code og code_challenge_method=S256 er faste for denne server.
OAuth 2.0 handler om autorisation (“må denne app kalde API'et?”). Det siger strengt taget intet om hvem brugeren er. OpenID Connect (OIDC) er et tyndt identitetslag ovenpå OAuth der standardiserer netop det:
scope=openid i authorize-kaldet → token-svaret indeholder også et id_token.amr (Authentication Methods References) i id_token fortæller login-styrken: pwd (adgangskode), otp (TOTP/recovery-kode), webauthn (passkey). Flere værdier = MFA gennemført./.well-known/openid-configuration — maskinlæsbar beskrivelse af serverens endpoints, så biblioteker kan konfigurere sig selv.To begreber der ofte springes over, men som afgør hvad et token kan, og hvordan en app skal opføre sig:
scope i authorize-kaldet er en mellemrums-separeret liste af “tilladelser” appen ønsker.
Auth-serveren afgør hvad den vil udstede — appen kan aldrig give sig selv mere adgang ved bare at bede pænt:
openid — aktiverer OIDC: token-svaret indeholder et id_token (med amr).profile / email — klassiske OIDC-scopes for navn og e-mail i id_token.offline_access — konventionen for “giv mig et refresh token” (denne server udsteder refresh tokens til registrerede klienter som del af flowet).role-claim) er hvad brugeren er i systemet (fx Admin). Et API tjekker typisk begge dele.
| Offentlig klient (public) | Fortrolig klient (confidential) | |
|---|---|---|
| Hvem | SPA i browseren, mobil-app, desktop-app | Backend/server-side webapp |
| Kan holde på en hemmelighed? | Nej — al kode kan læses af brugeren | Ja — koden kører på en server du kontrollerer |
| Bevis ved token-kald | PKCE code_verifier (engangs) | client_secret (fast, BCrypt-hashet i databasen) — gerne + PKCE |
| I denne platform | IsPublic=true, RequirePkce=true | client_secret sendes i token-kaldets form body |
Demo-SPA'en her er en offentlig klient (client_id=demo). Registrering af nye klienter sker i
databasen (ClientApps + redirect-URI-whitelist) — se AI-prompt-siden for hele checklisten.
Efter login har din app to (evt. tre) tokens med vidt forskellige roller og levetider:
Authorization: Bearer på hvert API-kald/oauth/token for at få nyt access tokenamr (login-metode/MFA)Tjekker session …
Knappen laver et ægte kald: dit access token udskiftes, og refresh tokenet roterer — se state-diagrammet ovenfor ske i virkeligheden.
Access token er en JWT: tre Base64URL-dele (header ·
payload · signatur). Indholdet kan alle læse — men signaturen
er lavet med auth-serverens private RSA-nøgle, og alle kan verificere den med den
offentlige nøgle fra /.well-known/jwks.json.
kid).iss (udsteder), aud (målgruppe) og exp (udløb).sub, role, email osv.Hele den historie — med live dekodning og verifikation af dit eget token — får du på JWT-siden.
Skriv hvad som helst og se det “forvandlet” til Base64URL — og straks dekodet igen. Alle kan gøre det samme med en JWT-payload. Der er ingen hemmelighed involveret.
—
—
Payload kan læses og ændres af alle — men signaturen passer kun på den originale. Redigér claims herunder
(giv dig fx selv "role": "Admin") og se hvad JWKS-verifikationen siger. E-mail vises maskeret.
To slags “logget ind” lever side om side, og det forvirrer ofte:
/oauth/authorize udsteder en kode uden at vise login-siden — det er derfor “log ind” nogle gange bare blinker forbi./signout?returnUrl=… så cookien også slettes. Ellers bliver du bare logget ind igen automatisk.
Brugere kan (valgfrit) beskytte kontoen med TOTP (authenticator-app) eller
passkeys (WebAuthn — Face ID, Windows Hello, hardware-nøgler). Auth-serveren håndterer det hele;
din app ser kun resultatet i amr-claimen.
mfa_pending-session kan
ikke gennemføre OAuth-flowet, så når din app modtager en kode, er brugeren garanteret fuldt verificeret.
Vil du vide hvor stærkt der blev logget ind, så bed om openid-scope og læs amr.
Hver regel i OAuth findes fordi nogen har udnyttet at den manglede. Her er de tre klassikere — og hvordan protokollen lukker dem.
state
Før login gemmer appen en tilfældig state. Callbacks uden det rigtige stempel afvises —
også selvom koden i dem er helt ægte (bare udstedt til angriberens session).
Gemt i “sessionStorage”: …
Tryk på en af knapperne.
Authorization-koden leveres via redirect — så den der kontrollerer redirect-målet, får koden.
Whitelist for denne demo-klient: …
—
localStorage — overlever lukket browser og kan læses ved XSSCors:SpaOrigins)| Begreb | Betydning |
|---|---|
issuer (iss) | Hvem udstedte tokenet — skal matche en forventet streng præcist. |
audience (aud) | Hvem tokenet er beregnet til. Dit API afviser tokens med forkert audience. |
claim | Ét felt i JWT-payloaden (fx sub, email, role). |
sub | Subject: brugerens stabile GUID. Brug den — aldrig e-mail — som nøgle i din database. |
scope | Hvad appen beder om lov til. openid aktiverer OIDC/id_token. |
grant | Måden man får tokens på. Her: authorization_code og refresh_token. |
bearer | “Ihændehaver”: den der har tokenet, har adgangen. Derfor korte levetider + HTTPS. |
PKCE | Proof Key for Code Exchange — engangs-hemmelighed der sikrer code-flowet uden client_secret. |
JWKS | JSON Web Key Set: serverens offentlige nøgler til at verificere JWT-signaturer. |
kid | Key ID i JWT-headeren — peger på den rigtige nøgle i JWKS ved nøglerotation. |
amr | Authentication Methods References: pwd, otp, webauthn — hvordan brugeren loggede ind. |
redirect_uri | Din callback-URL. Whitelistes på serveren med eksakt match — ellers afvises flowet. |
public client | App der ikke kan holde på en hemmelighed (SPA, mobil). Beviser sig med PKCE i stedet for client_secret. |
confidential client | Server-side app med client_secret. Hemmeligheden bor på serveren — aldrig i browseren. |
CSRF | Cross-Site Request Forgery: fremmed side udløser handlinger i din session. I OAuth stoppes det af state. |
XSS | Cross-Site Scripting: fremmed JavaScript på din side — kan læse localStorage/sessionStorage. Derfor korte tokens og forsigtig lagring. |
Seks spørgsmål, ét svar pr. spørgsmål, forklaring med det samme.
/.well-known/mercantec-auth.jsonDemo ved MAGS (underviser) — ikke Mercantecs officielle produkt. [email protected]