Web-sovelluksen kirjautuminen
tunnuspalvelu on standardinmukainen OpenID Connect 1.0 -tarjoaja. Tuettu
kirjautumisvuo on authorization code -flow PKCE:llä — implisiittistä tai
hybridi-flow’ta ei tueta (response_types_supported: ["code"]).
1. Discovery
Osio nimeltä “1. Discovery”Hae tenantin metadata pyynnön alussa (tai kertakäynnistyksessä ja välimuistita se sovelluksessasi):
GET https://id.esimerkki.fi/.well-known/openid-configurationVastauksesta tarvitset ainakin authorization_endpoint, token_endpoint,
userinfo_endpoint ja jwks_uri. Koko kenttälistan selitykset:
Discovery-dokumentti.
2. Ohjaa käyttäjä /authorize-osoitteeseen
Osio nimeltä “2. Ohjaa käyttäjä /authorize-osoitteeseen”| Parametri | Pakollinen | Selitys |
|---|---|---|
response_type |
kyllä | Aina code |
client_id |
kyllä | Clientin tunniste |
redirect_uri |
kyllä | Täsmää merkilleen clientille rekisteröityyn URIin |
scope |
kyllä | Väliviivalla eroteltu lista, esim. openid profile email. Oletus jos puuttuu: openid |
state |
suositellaan | CSRF-suojaus, palautetaan sellaisenaan callbackissa |
nonce |
suositellaan (openid) | Palautetaan ID tokenin nonce-claimissa, replay-suojaksi |
code_challenge |
pakollinen public clienteille | PKCE-haaste, ks. alla |
code_challenge_method |
pakollinen jos code_challenge annettu |
Ainoa tuettu arvo on S256 |
prompt |
valinnainen | none | login | consent | select_account — OIDC Core §3.1.2.1 |
max_age |
valinnainen | Sekunteina: jos olemassa oleva sessio on tätä vanhempi, käyttäjä pakotetaan kirjautumaan uudelleen |
PKCE: generoi satunnainen code_verifier, laske
code_challenge = BASE64URL(SHA256(code_verifier)) ja lähetä
code_challenge_method=S256. Public clientit (ilman client_secret-arvoa)
vaativat PKCE:n aina; confidential clientit voidaan tarvittaessa pakottaa
käyttämään sitä myös.
prompt=none palauttaa error=login_required, jos käyttäjä ei ole
kirjautunut tai istunto ei kelpaa — käytä tätä hiljaiseen
uudelleentodennukseen (esim. iframe-tarkistus). prompt=login ja
prompt=select_account ohittavat olemassa olevan istunnon ja pakottavat
uuden kirjautumisen.
GET https://id.esimerkki.fi/authorize ?response_type=code &client_id=esimerkki-web &redirect_uri=https%3A%2F%2Fapp.esimerkki.fi%2Fcallback &scope=openid%20profile%20email &state=<satunnainen> &nonce=<satunnainen> &code_challenge=<base64url(sha256(code_verifier))> &code_challenge_method=S256Käyttäjä kirjautuu tunnuspalvelun kautta (magic link, passkey, Google tai
muu tenantille sallittu tapa — ks.
Kirjautumistavat) ja ohjataan takaisin
redirect_uri-osoitteeseen kyselyparametrilla code (ja state, jos
lähetit sellaisen).
Virhetilanteet palautuvat joko HTML-virhesivuna (tuntematon client_id tai
rekisteröimätön redirect_uri — näihin ei voi luottaa uudelleenohjauksessa)
tai kyselyparametreina redirect_uri-osoitteeseen (kun se on jo validoitu).
Täysi virhekoodilista: Virhekoodit.
3. Vaihda koodi tokeneihin
Osio nimeltä “3. Vaihda koodi tokeneihin”POST https://id.esimerkki.fi/tokenContent-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<authorize-vastauksen code>&redirect_uri=https://app.esimerkki.fi/callback&code_verifier=<PKCE-vaiheen alkuperäinen arvo>Clientin todennus — kaksi tuettua tapaa, valitse toinen:
client_secret_basic:Authorization: Basic base64(client_id:client_secret)client_secret_post:client_idjaclient_secretlomakekenttinä samassa pyynnössä
Public clientit (ei client_secret-arvoa lainkaan) todennetaan pelkällä
PKCE:llä — code_verifier riittää.
Onnistunut vastaus:
{ "access_token": "eyJhbGciOiJFUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "id_token": "eyJhbGciOiJFUzI1NiIs...", "scope": "openid profile email"}Jos scope sisälsi offline_access ja clientille on sallittu
refresh_token-grant, vastauksessa on myös refresh_token.
4. Validoi ID token
Osio nimeltä “4. Validoi ID token”ID token on ES256-allekirjoitettu JWT. Vähintään:
- Hae allekirjoitusavain
jwks_uri-osoitteesta (kidtokenin headerista kertoo, mikä avain). - Tarkista allekirjoitus.
- Tarkista
iss== tenantin issuer-URL. - Tarkista
aud== omaclient_id. - Tarkista
expei ole mennyt umpeen. - Jos lähetit
nonce-arvon authorize-pyynnössä, tarkista että se täsmää.
Useimmat OIDC-kirjastot (esim. openid-client, Auth.js) tekevät nämä
tarkistukset automaattisesti kun annat niille discovery-URL:n. Täysi
claim-taulukko: Tokenit ja claimit.
5. Käytä access tokenia
Osio nimeltä “5. Käytä access tokenia”GET https://id.esimerkki.fi/userinfoAuthorization: Bearer <access_token>Palauttaa myönnetyistä scopeista riippuvat claimit JSON-muodossa (sub aina
mukana). Sama endpoint hyväksyy myös DPoP-sidotun tokenin
(Authorization: DPoP <access_token> + DPoP-header) — ks.
Mobiili- ja natiivisovellukset.
6. Refresh token -käyttö
Osio nimeltä “6. Refresh token -käyttö”Refresh token on opaakki (ei JWT — sitä ei voi dekoodata) ja rotatoituu
joka käytöllä: jokainen /token-kutsu grant_type=refresh_token-arvolla
kumoaa käytetyn tokenin ja palauttaa uuden. Jos kumottua (jo käytettyä)
refresh tokenia yritetään käyttää uudelleen, koko token-perhe kumotaan —
tämä havaitsee varastetun tokenin toisen käyttöyrityksen ja katkaisee kaikki
kyseisen käyttäjä+client-parin sessiot.
POST https://id.esimerkki.fi/tokenContent-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=<edellinen refresh_token>7. Uloskirjautuminen
Osio nimeltä “7. Uloskirjautuminen”RP-Initiated Logout -endpoint:
GET https://id.esimerkki.fi/logout ?id_token_hint=<viimeisin ID token> &post_logout_redirect_uri=https://app.esimerkki.fi| Parametri | Selitys |
|---|---|
id_token_hint |
Aiemmin myönnetty ID token — tunnuspalvelu lukee siitä käyttäjän ja clientin |
client_id |
Vaihtoehto id_token_hint:lle, jos sitä ei ole saatavilla |
post_logout_redirect_uri |
Minne käyttäjä ohjataan uloskirjautumisen jälkeen — täytyy olla rekisteröity clientille |
Jos clientille on määritetty backchannel_logout_uri, tunnuspalvelu lähettää
sinne myös taustakanavan logout_token-JWT:n jokaiselle sessioon liittyvälle
clientille — hyödyllinen, jos ylläpidät omaa palvelinpuolen sessiovarastoa
etkä voi luottaa pelkkään selaimen uloskirjautumiseen.
8. Step-up: tuore tunnistus ennen arkaluonteisia toimia
Osio nimeltä “8. Step-up: tuore tunnistus ennen arkaluonteisia toimia”Jokaisessa ID tokenissa on kaksi claimia, joiden avulla sovellus voi vaatia vahvemman tai tuoreemman tunnistuksen vain silloin, kun toimi sitä tarvitsee:
auth_time: hetki, jolloin käyttäjä oikeasti tunnistautui. Säilyy, kun tunnuspalvelu käyttää olemassa olevaa istuntoa uudelleen (SSO).amr: tunnistustapa RFC 8176 -arvoina. Passkey tuottaa["hwk", "user", "mfa"], magic link["otp"], Google ei mitään. Koko taulukko: Tokenit ja claimit.
Tallenna molemmat omaan istuntoosi kirjautumisen yhteydessä. Kun käyttäjä yrittää arkaluonteista toimea (maksuasetukset, API-avaimen luonti, oikeuksien muutos), tarkista kaksi asiaa:
amrsisältää"mfa", eli viimeisin tunnistus oli passkey tai magic link ja TOTP.auth_timeon riittävän tuore, esimerkiksi enintään 300 sekuntia vanha.
Jos kumpi tahansa ei täyty, ohjaa käyttäjä /authorize-osoitteeseen
max_age-parametrilla. Tunnuspalvelu pakottaa uuden tunnistuksen, jos
istunto on parametria vanhempi, ja palauttaa uuden ID tokenin tuoreilla
auth_time- ja amr-arvoilla.
GET https://id.esimerkki.fi/authorize ?response_type=code &client_id=esimerkki-web &redirect_uri=https%3A%2F%2Fapp.esimerkki.fi%2Fcallback &scope=openid &max_age=300 &state=<satunnainen> &nonce=<satunnainen>Auth.js:llä sama onnistuu välittämällä parametri signIn-kutsulle:
await signIn("tunnuspalvelu", { redirectTo: "/settings/payout" }, { max_age: "300" });Vertaa amr-arvoja standardin sanastoon, älä omiin nimiin kuten passkey
tai email_code. Puuttuva amr (Google-kirjautuminen) ei täytä
passkey-vaatimusta, ja näin pitääkin olla: tunnuspalvelu ei voi todentaa
ulkoisen tarjoajan tunnistustapaa.
Esimerkit
Osio nimeltä “Esimerkit”# 1. Discoverycurl -s https://id.esimerkki.fi/.well-known/openid-configuration | jq .
# 2. Avaa /authorize selaimessa (katso yllä oleva URL-rakenne),# kirjaudu sisään, kaappaa `code` redirectistä
# 3. Vaihda koodi tokeneihincurl -s -X POST https://id.esimerkki.fi/token \ -d grant_type=authorization_code \ -d code="$CODE" \ -d redirect_uri=https://app.esimerkki.fi/callback \ -d code_verifier="$CODE_VERIFIER" \ -u esimerkki-web:"$CLIENT_SECRET"
# 4. Kutsu userinfocurl -s https://id.esimerkki.fi/userinfo \ -H "Authorization: Bearer $ACCESS_TOKEN"Täysi ajettava versio (mukaan lukien PKCE-generointi ja callback-kaappaus): Koodiesimerkit.
import NextAuth from "next-auth";
export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [ { id: "tunnuspalvelu", name: "Kirjaudu sisään", type: "oidc", issuer: process.env.AUTH_TUNNUSPALVELU_ISSUER, // https://id.esimerkki.fi clientId: process.env.AUTH_CLIENT_ID, clientSecret: process.env.AUTH_CLIENT_SECRET, }, ], callbacks: { async jwt({ token, account }) { if (account) { token.accessToken = account.access_token; token.idToken = account.id_token; } return token; }, async session({ session, token }) { session.accessToken = token.accessToken as string; return session; }, },});import { handlers } from "@/auth";export const { GET, POST } = handlers;Auth.js hakee discovery-dokumentin automaattisesti issuer-arvosta eikä
vaadi manuaalista endpoint-konfigurointia. Lisää scope-parametri
authorization.params-kenttään, jos tarvitset orgs/roles/phone-scopet
laajennettuun ID tokeniin — ks. Tokenit ja claimit.
Katso myös
Osio nimeltä “Katso myös”- Tokenit ja claimit — access/ID/refresh tokenin muoto,
amr-arvot - Mobiili- ja natiivisovellukset — public client, PKCE, DPoP
- Virhekoodit — kaikki
/authorizeja/token-virheet - Avaimet ja allekirjoitus — miten JWKS ja avainten kierto toimivat
- Kirjautumistavat — mitä kirjautumistapoja tenantille voi sallia