# OpenID Connect

## Vad det är

OpenID Connect är en standard som låter en applikation få veta vem en person är
från en tjänst som just har loggat in personen. Specifikationen kallar sig själv
ett enkelt identitetslager ovanpå OAuth 2.0, och det är precis så den är byggd:
samma omdirigeringar, endpoints och tokens som i OAuth, plus en token till, en
endpoint till och reglerna för hur de kontrolleras. I vardagligt tal förkortas
den ofta till OIDC. Kärnspecifikationen blev slutgiltig i februari 2014. Den
aktuella texten, där den andra omgången rättelser har arbetats in, är daterad
den 15 december 2023 och förvaltas av en arbetsgrupp inom OpenID Foundation.

OAuth på egen hand svarar på frågan ”vad får den här applikationen göra?”.
Applikationen får en åtkomsttoken som är avsedd för ett API, och det är inte
meningen att applikationen själv ska läsa den. OpenID Connect ställer dessutom
frågan ”vem loggade in?” och besvarar den med en ID-token: en signerad JSON Web
Token, adresserad till applikationen själv, som talar om vem personen är, vem
som går i god för det, när token utfärdades och vilken applikation den är avsedd
för.

För den här uppgiften ger specifikationen OAuth-rollerna nya namn. En OpenID
Provider (OP) är en auktoriseringsserver som autentiserar personer och utfärdar
ID-tokens. En Relying Party (RP) är OAuth-klienten som behöver veta vem personen
är, och personen själv kallas End-User. En begäran blir en OpenID
Connect-begäran genom att `openid` finns med i scopet, och det värdet är
obligatoriskt: saknas det är beteendet enligt specifikationens egna ord helt
ospecificerat.

Andra scope-värden ber om profiluppgifter. `profile` täcker namn, smeknamn,
bild, språkinställning och liknande claims, `email` ger `email` och
`email_verified`, och `address` och `phone` gör det namnen säger.
Applikationen kan hämta de här claims från leverantörens UserInfo-endpoint, ett
helt vanligt OAuth-skyddat API som den anropar med åtkomsttoken.

Det finns tre sätt att köra det. Authorization code-flödet skickar bara en
kortlivad kod genom webbläsaren och hämtar själva tokens direkt mellan servrarna.
Implicit-flödet levererar tokens rakt i omdirigeringen, och hybridflödet blandar
de två. En kompletterande specifikation, OpenID Connect Discovery 1.0, låter
applikationen slå upp alla endpoints och signeringsnycklar utifrån en enda
adress.

## Varför det spelar roll

Skälet att införa det är single sign-on. Med en identitetsleverantör framför
många applikationer har människor en uppsättning inloggningsuppgifter,
applikationerna lagrar inga av dem, och reglerna för hur man loggar in, från
lösenordskrav till en extra faktor, bestäms på ett ställe, hos leverantören.
Spärras ett konto hos leverantören kan det inte längre starta en ny session i
någon av applikationerna som förlitar sig på den, och därmed blir offboarding
ett enda steg i stället för en checklista.

Standarden täpper också till ett hål som team faller i när de bygger inloggning
på ren OAuth. RFC 6749 varnar för att en publik klient i implicit-flödet inte
kan se vilken klient en åtkomsttoken utfärdades till. En klient som utgår från
att bara resursägaren kan ge den en giltig token går därför att lura: en
angripare med en token som utfärdats till en annan applikation lämnar fram den
och tas för personen bakom. En ID-token anger i sitt `aud`-claim vilken
applikation den är till för, och applikationen kontrollerar det.

Eftersom det är en standard går arbetet att återanvända. Specifikationen
utlovar profiluppgifter på ett interoperabelt sätt, så koden som loggar in
människor via en leverantör kan logga in dem via en annan också. Med Discovery
lär sig applikationen leverantörens endpoints och signeringsnycklar utifrån
issuer-URL:en i stället för från inställningar som någon har skrivit av för
hand.

Till sist ger OpenID Connect varje person en stabil identifierare.
Specifikationen säger uttryckligen att paret `iss` (utfärdaren) och `sub`
(subjektidentifieraren) är de enda claims som en applikation kan lita på som
stabil identifierare för en person. Värdet i `sub` är unikt hos utfärdaren,
återanvänds aldrig och är högst 255 ASCII-tecken långt. En leverantör kan till
och med ge varje applikation ett eget `sub`, en så kallad parvis identifierare,
så att applikationerna inte kan koppla ihop det en person gör utan tillstånd.

## Så ser det ut i praktiken

Ta en webbapplikation som är registrerad hos en OpenID Provider på
`https://server.example.com` med klient-ID:t `s6BhdRkqt3`; värdena nedan är
hämtade från specifikationens egna exempel. Applikationen börjar med att hämta
leverantörens konfiguration från den fasta adressen under utfärdaren:

```http
GET /.well-known/openid-configuration HTTP/1.1
Host: server.example.com
```

Svaret är ett JSON-dokument. I förkortad form:

```json
{
  "issuer": "https://server.example.com",
  "authorization_endpoint": "https://server.example.com/connect/authorize",
  "token_endpoint": "https://server.example.com/connect/token",
  "userinfo_endpoint": "https://server.example.com/connect/userinfo",
  "jwks_uri": "https://server.example.com/jwks.json",
  "scopes_supported": ["openid", "profile", "email", "address", "phone"],
  "id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"]
}
```

Discovery kräver att `issuer` i dokumentet är identiskt med den issuer-URL som
applikationen satte framför den fasta sökvägen, och applikationen avbryter om de
skiljer sig åt. Därefter skickar den personens webbläsare till
auktoriseringsendpointen med code-flödet, scopet `openid` och två slumpmässiga
värden som den har sparat tillsammans med webbläsarsessionen: `state` för
omdirigeringen och `nonce` för ID-token. En publik klient skulle dessutom skicka
PKCE-parametrarna, som RFC 9700 gör obligatoriska för den; de är utelämnade här
för att hålla raderna korta.

```http
GET /connect/authorize?response_type=code&client_id=s6BhdRkqt3
    &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
    &scope=openid%20profile%20email
    &state=af0ifjsldkj&nonce=n-0S6_WzA2Mj HTTP/1.1
Host: server.example.com
```

Leverantören loggar in personen och skickar tillbaka webbläsaren med en kod, som
applikationen löser in vid token-endpointen precis som i vanlig OAuth.
Skillnaden ligger i svaret, som nu har en `id_token` bredvid åtkomsttoken
(förkortat här):

```json
{
  "access_token": "SlAV32hkKG",
  "token_type": "Bearer",
  "refresh_token": "8xLOxBtZp8",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc..."
}
```

ID-token består av tre base64url-delar åtskilda av punkter. Den första avkodas
till `{"alg":"RS256","kid":"1e9gdk7"}`: signeringsalgoritmen och ID:t för
nyckeln i leverantörens publicerade nyckeluppsättning. Den mittersta delen
innehåller själva claims, och i specifikationens exempel ser de ut så här:

```json
{
  "iss": "https://server.example.com",
  "sub": "24400320",
  "aud": "s6BhdRkqt3",
  "nonce": "n-0S6_WzA2Mj",
  "exp": 1311281970,
  "iat": 1311280970,
  "auth_time": 1311280969,
  "acr": "urn:mace:incommon:iap:silver"
}
```

Innan applikationen litar på ett enda ord av detta validerar den token, och
specifikationen räknar upp stegen. `iss` måste stämma exakt med leverantörens
utfärdare. `aud` måste innehålla applikationens eget klient-ID och inga
mottagare som den inte litar på. Signaturen måste gå att verifiera med
leverantörens nycklar från `jwks_uri` och med algoritmen i headern; kom token,
som här, direkt från token-endpointen över TLS kan en kontroll av serverns
TLS-certifikat ersätta signaturkontrollen. Klockan måste vara före `exp`, och
`nonce` måste vara det värde som applikationen själv skickade. Först då loggar
den in personen, med `https://server.example.com` plus `24400320` som nyckel,
och använder åtkomsttoken för att hämta e-post och profiluppgifter från
UserInfo-endpointen om den behöver dem.

En leverantör för många system är lika mycket en organisatorisk förändring som
en teknisk. I ett av projekten här på webbplatsen,
[behörighetshantering i M365 och SSO](/sv/portfolio/led-access-management-across-m365-and-sso/),
samlades identiteterna och åtkomsten i M365, 1Password, Red Hat SSO och OKTA
SSO. Principen om minsta behörighet upprätthölls, och onboarding och offboarding
standardiserades, så att åtkomst återkallades snabbt och konsekvent i stället
för att ligga kvar efter att någon hade gått vidare.

## Var det går fel

**E-postadressen används som kontonyckel.** Det är det självklara valet och det
felaktiga. Specifikationen säger att en utfärdare med tiden kan ge samma
e-postadress till en annan person, och att `email`, `phone_number`,
`preferred_username` och `name` inte får användas som unika identifierare. En
applikation som slår upp människor på e-post kan ge en persons konto till nästa
person som får adressen. Spara `iss` och `sub` tillsammans som nyckel, behåll
e-posten som ett vanligt attribut, och kontrollera att `email_verified` är sant
innan adressen används till något alls.

**En åtkomsttoken tas som bevis på vem som loggade in.** Vissa applikationer tar
emot en åtkomsttoken, anropar ett API med den och behandlar svaret som en
inloggning. Token kan ha utfärdats till en helt annan applikation, och då är det
just det angrepp där någon utger sig för att vara någon annan som RFC 6749
varnar för. Logga in människor på ID-token, och först när ni har bekräftat att
`aud` innehåller ert eget klient-ID.

**ID-token avkodas men kontrolleras aldrig.** Att läsa ut claims ur en token
kräver en base64url-avkodning och ingen nyckel alls, och det är just det som gör
det frestande. En token där signaturen, `iss`, `aud` eller `exp` inte
kontrolleras kan vara förfalskad, tillhöra en annan applikation eller ha gått
ut. Använd ett bibliotek som underhålls, ge det utfärdaren och klient-ID:t, och
låt det köra alla steg i specifikationens lista, inte bara de bekväma.

**Nonce saknas, är fast eller jämförs aldrig.** Utan den kan en ID-token som
snappats upp från en inloggning spelas upp igen i en annan. RFC 9700 tillägger
att nonce måste höra till en bestämd inloggning och vara bunden till webbläsaren
som startade den. Specifikationen föreslår ett sätt att göra det: spara ett
kryptografiskt slumpmässigt värde i en HttpOnly-sessionscookie, skicka dess hash
som `nonce` och jämför de två när ID-token kommer tillbaka.

**Signeringsnycklarna cachas för evigt.** Leverantörer byter nycklar, och en
applikation som håller fast vid gårdagens nyckeluppsättning avvisar varje token
som är signerad med den nya. Specifikationen förväntar sig att leverantören
publicerar nycklarna på `jwks_uri` och märker varje token med ett `kid`, och att
applikationen hämtar nyckeluppsättningen på nytt när den stöter på ett `kid` som
den inte känner igen. Cacha gärna nycklarna, men låt ett okänt `kid` vara en
signal att hämta dem på nytt och inte ett skäl att avvisa inloggningen.

**Två tenants på samma värd behandlas som en utfärdare.** En leverantör kan köra
flera utfärdare på samma värd, och sökvägen är en del av
utfärdaridentifieraren: subjektet `1234` hos `https://example.com` och subjektet
`1234` hos `https://example.com/sales` är två olika personer. Spara hela
issuer-strängen, jämför den tecken för tecken, och korta aldrig ned den till ett
värdnamn.

## Läs mer

- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
  — ID-token, de tre flödena, standardclaims och valideringsstegen, i utgåvan
  med rättelseomgång 2 från december 2023.
- [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
  — konfigurationsdokumentet på den fasta adressen och regeln att dess issuer
  måste vara identisk med den issuer-URL det hämtades under.
- [RFC 6749: The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749)
  — ramverket som OpenID Connect bygger på, med varningen i avsnitt 10.16 mot
  att använda åtkomsttokens för att identifiera personer.
- [RFC 7519: JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519) —
  tokenformatet som alla ID-tokens använder.
- [RFC 9700: Best Current Practice for OAuth 2.0 Security](https://www.rfc-editor.org/rfc/rfc9700)
  — aktuella rekommendationer om PKCE, nonce och omdirigering, som gäller för
  OpenID Connect lika mycket som för OAuth.

<https://engineer.company/sv/glossary/openid-connect/>
