1. Eksempel på tjeneste implementering
1.1 Før du begynder
Unilogin benytter Authorization Code Flow til login, som er den sikre løsning når der logges ind med OIDC. For at tilknytte sin tjeneste, skal man have følgende informationer klar:
- Client Id: Dette er udfyldt af tjenesteudbyderen i metadata.
- Eksempelvis "https://tjeneste.domaene.dk/min-tjeneste" eller "VerdensBedsteTjeneste"
- Redirect Uris: Disse er udfyldt af tjenesteudbyderen i metadata, og er uri'er som brugeren må redirectes til.
- Eksempelvis "https://tjeneste.domaene.dk/du-er-nu-logget-ind"
- Eksempelvis "https://tjeneste.domaene.dk/callback-from-unilogin"
- [REQUIRED] Client Secret: Denne UUID sendes tilbage fra Unilogin til tjenesteudbyderen (dig), når en tjeneste er oprettet.
- Eksempelvis "8943b0cc-e9af-11ed-a05b-0242ac120003
Bemærk client_secret skal anvendes hvis Refresh Token benyttes
- Eksempelvis "8943b0cc-e9af-11ed-a05b-0242ac120003
Til reference kan følgende diagram benyttes, som illustrerer et simpelt login og visualiserer dataflow hvor et udvalg af claims ønskes returneret:
1.2 Endpoints til OIDC konfiguration
Den aktuelle konfiguration til hhv. Test og Produktion er tilgængelig her:
- Test: https://et-broker.unilogin.dk/auth/realms/broker/.well-known/openid-configuration
- Produktion: https://broker.unilogin.dk/auth/realms/broker/.well-known/openid-configuration
Dette endpoint returnerer den aktuelle konfiguration af OIDC protokollen. Vær opmærksom på at specifikke krav kan være implementeret udenom konfigurationen af protokollen og påvirkes af intern navngivning, hvorfor dette endpoint kun er til hjælp.
Endpoints til openid-configuration for testmiljøet (ET)
1.3 Log ind via OIDC Tjeneste
Når en bruger skal logge ind via en tjeneste, sker det i første omgang med en simpel redirect (get request) til unilogin-url med parametre som identificerer clienten/tjenesten.
OBS: Client og Tjeneste bruges her som synonymer, da en "client" i koden repræsenterer en tjeneste.
Til opbygning af redirect-link til unilogin, skal du bruge følgende værdier:
- [REQUIRED] url til eksternt Unilogin testmiljø: https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/auth
- [REQUIRED] client_id: Det client id, som du udfyldte i metadata
- [REQUIRED] redirect_uri: Det endpoint på dit domæne, som tager imod "session_state" og "code", når Unilogon redirecter brugeren tilbage til dit domæne efter login. Dette skal være udfyldt i metadata, fx "https://tjeneste.domaene.dk/callback-from-unilogin"
- [REQUIRED] response_type: Skal altid være "code"
- [REQUIRED] scope: Skal altid være "openid". Dette er for at få ID Token med i response.
- [REQUIRED] nonce: Vilkårlig genereret string, minimum 128 bit
- [REQUIRED] state: Vilkårlig genereret string på minimum 128 bit
- [REQUIRED] code_challenge: URLencoded sha256 hash af code_verifier. Code_verifier skal være præcis 128 bit (antal tegn) genereret ascii værdi, som skal benyttes i senere request. Det er vigtigt, at der genereres en ny code_verifier for hvert request.
- [REQUIRED] code_challenge_method: Altid "S256"
- [OPTIONAL] acr_values: options
- "https://data.gov.dk/concept/core/nsis/loa/Low"
- Bemærk: "https://data.gov.dk/concept/core/nsis/loa/Substantial" bliver muligt når Unilogin Broker NSIS godkendes på niveau Betydelig.
- "EnFaktor",
- "ToFaktor",
- "VoksenVerificeret"
- "https://data.gov.dk/concept/core/nsis/loa/Low"
- [OPTIONAL] prompt: Denne skal sættes til "login". Dette skal bruges hvis man ønsker at tvinge brugeren til at logge på igen. Kan fx bruges i forbindelse med step-up fra "EnFaktor" til "ToFaktor", og på sigt fra "Low" til "Substantial". Se eksempel i fane to nedenfor
https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/auth? response_type=code& state=0123456789fedabe& client_id=https://tjeneste.domaene.dk/min-tjeneste& redirect_uri=https://tjeneste.domaene.dk/callback-from-unilogin& scope=openid& acr_values=ToFaktor& nonce=xc12as45gj8723 code_challenge=TshES7OPwSW3N9KLrGePl79Ks_YuD9FqRsb5VEIJeWs& code_challenge_method=S256
https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/auth? response_type=code& state=0123456789fedabe& client_id=https://tjeneste.domaene.dk/min-tjeneste& redirect_uri=https://tjeneste.domaene.dk/callback-from-unilogin& scope=openid& acr_values=ToFaktor& nonce=xc12as45gj8723 code_challenge=TshES7OPwSW3N9KLrGePl79Ks_YuD9FqRsb5VEIJeWs& code_challenge_method=S256& prompt=login
https://sso.emu.dk/unilogin/login.cgi? id=min_sso_tjeneste& auth=7bb51908ee427d9a54c3c54d412990ba& path=aAB0AHQAcABzADoALwAvAHQAagBlAG4AZQBzAHQAZQAuAGQAbwBtAGEAZQBuAGUALgBkAGsALwBjAGEAbABsAGIAYQBjAGsALQBmAHIAbwBtAC0AdQBuAGkAbABvAGcAaQBuAA==
En login-knap kan således laves simpelt:
<a href="https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/auth?response_type=code&state=0123456789fedabe&client_id=https://tjeneste.domaene.dk/min-tjeneste&redirect_uri=https://tjeneste.domaene.dk/callback-from-unilogin&scope=openid&acr_values=ToFaktor&nonce=xc12as45gj8723&code_challenge=TshES7OPwSW3N9KLrGePl79Ks_YuD9FqRsb5VEIJeWs&code_challenge_method=S256">Log ind</a>
Du kan teste at din url fungerer ved at åbne den i en browser, og se at den redirecter dig til unilogins loginvælger, på samme måde som følgende link:
Test login
1.3.1 Hvordan genereres korrekt code_challenge og code verifier
Code_verifier er den værdi, som code_challenge er beregnet ud fra. Code_challenge er en URLencoded sha256 hash af code_verifier. Code_verifier skal være præcis 128 bit (antal tegn) genereret ascii værdi.
Den findes en række forskellige kodesprog der kan generere en korrekt værdi, læs mere her https://auth0.com/docs/get-started/authentication-and-authorization-flow/call-your-api-using-the-authorization-code-flow-with-pkce
Nedenfor ses et Java eksempel:
private String generateCodeChallenge(String codeVerifier){ try{ byte[] bytes = codeVerifier.getBytes("US-ASCII"); MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(bytes, 0, bytes.length); byte[] digest = md.digest(); return Base64.encodeBase64URLSafeString(digest); } catch (Exception e) { System.out.println(e); } return null; }
1.3.2 Modtag Access Token med Authorization Code
Når brugeren er logget ind, redirectes de tilbage til den redirect_uri på dit domaene, som du angav i login-kaldet's uri, sammen med to parametre:
- session_state
- code
- state
I eksemplet ovenfor, vil brugeren derfor sendes tilbage til (med eksempel-værdier i session_state og code):
https://tjeneste.domaene.dk/callback-from-unilogin? session_state=1234-5678-91011& code=f880f423-7a5b-4700-ae31-514714c2c895.a017272d-9244-4d78-a1d5-e77955697986.860561ac-b2c5-4ca8-9c7e-25d551282209& state=0123456789fedabe
Du skal derfor oprette dit endpoint, således at det kan læse disse tre get-parametre. session_state skal ikke bruges til noget ift.. , men code skal benyttes i næste trin til at modtage Access Token.
For at modtage information om den bruger som er logget ind (AssuranceLevel, UniId, CVR osv.), samt at genindlæse eller lukke sessionen (logge ud), skal du hente et Access Token fra Unilogin. Dette hentes med et post-request, som opbygges med det data du nu har til rådighed.
1.3.2.1 Til post request skal du bruge:
- token request url: https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/token
- code: Det code-parameter, som kom fra login requestet
- grant_type: Skal altid være "authorization_code"
- client_id: Det client id, som du udfyldte i metadata
- redirect_uri: Den URI, som kan tage imod et svar fra unilogin
- code_verifier: Den værdi, som code_challenge er beregnet ud fra. Code_verifier skal være præcis 128 bit (antal tegn) genereret ascii værdi.
- client_secret: Den secret, som du har modtaget efter registrering af tjeneste.
Bemærk: Dette er et server-til-server endpoint og kan ikke kaldes via browseren
Eksempel på et post-request i pseudokode vil derfor se således ud:
httpPost ({ url: "https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/token", body: { code: "f880f423-7a5b-4700-ae31-514714c2c895.a017272d-9244-4d78-a1d5-e77955697986.860561ac-b2c5-4ca8-9c7e-25d551282209", grant_type: "authorization_code", client_id: "https://tjeneste.domaene.dk/min-tjeneste", redirect_uri: "https://tjeneste.domaene.dk/callback-from-unilogin", code_verifier: "GXw9KzMr2K7tq2ueMankiQmw69AZp9blFf7LzT2EIzJYA7Xtp9c9DyJiy9v4Xne9VI3dfYObP0of1cT5ZDHTEqZlRFbsPBQrWZCKO4QkAIsssPAAq7EL6YZLBXSFoOJc", client_secret: "8943b0cc-e9af-11ed-a05b-0242ac120003" } })
1.3.2.2 Http-response med Access Token og Refresh Token
Dette vil resultere i et http-response som ser således ud (Access Token og Refresh Token er forkortet i eksemplet).
Det er Access Token fra dette svar, du skal bruge i næste kald, til at hente brugerinformation.
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lk..........ib5jzP0pC7bv-3qZzAc3NcfyvR", "expires_in": 300, "refresh_expires_in": 1800, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAi..........buv3TW_v3uxbInNbCh_1StuluCRJWUCY", "token_type": "bearer", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI..........FXe2Jpib2owilfEBII7zW79Urqu101HiVf7" "not-before-policy": 1682601559, "nonce": "xc12as45gj8723", "session_state": "b676ee79-0538-43bd-a5a8-9faf2f8d9f09", "scope": "openid" }
1.3.3 Modtag brugerinformationer med Access Token
Med Access Token kan ud hente brugerinformation ud, via endnu et post request. Dette post request udføres som det tidligere.
Til post request skal du bruge:
- token request url: https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/token/introspect
- token: Access token fra tidligere request
- client_id: Det client id, som du udfyldte i metadata
- client_secret: Den secret, som du har modtaget efter registrering af tjeneste
Eksempel på et post-request i pseudokode vil derfor se således ud:
httpPost ({ url: "https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/token/introspect", body: { token: "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lk..........ib5jzP0pC7bv-3qZzAc3NcfyvR", client_id: "https://tjeneste.domaene.dk/min-tjeneste", client_secret: "8943b0cc-e9af-11ed-a05b-0242ac120003" } })
1.3.3.1 Http-response med Access Token
Dette vil resultere i et http-response som ser således ud:
{ "exp": 1682942294, "iat": 1682941994, "auth_time": 1682941994, "jti": "dc451345-ebb4-4dbd-a6a4-cff8d04839ae", "iss": "https://et-broker.unilogin.dk/auth/realms/broker", "sub": "db829872-f9c0-435f-ac71-9ca38a015fa1", "typ": "Bearer", "azp": "https://tjeneste.domaene.dk/min-tjeneste", "session_state": "938a53ee-ce1f-44e7-86d6-9472c99e77e2", "nonce": "xc12as45gj8723", "acr": "https://data.gov.dk/concept/core/nsis/loa/Low", "scope": "openid", "spec_ver": "OIDC.3.0-UNILOGIN", "unilogin_loa": "EnFaktor", "aktoer_gruppe": "Medarbejder", "loa": "2", "uniid": "1000013b36", "client_id": "https://tjeneste.domaene.dk/min-tjeneste" }
{ "exp": 1682942294, "iat": 1682941994, "auth_time": 1682941994, "jti": "dc451345-ebb4-4dbd-a6a4-cff8d04839ae", "iss": "https://et-broker.unilogin.dk/auth/realms/broker", "sub": "db829872-f9c0-435f-ac71-9ca38a015fa1", "typ": "Bearer", "azp": "https://tjeneste.domaene.dk/min-tjeneste", "session_state": "938a53ee-ce1f-44e7-86d6-9472c99e77e2", "nonce": "xc12as45gj8723", "acr": "https://data.gov.dk/concept/core/nsis/loa/Low", "scope": "openid", "spec_ver": "OIDC.3.0-UNILOGIN", "unilogin_loa": "ToFaktor", "aktoer_gruppe": "Medarbejder", "loa": "3", "cvr": "13223459", "rid": "781914123", "uniid": "1000013b36", "client_id": "https://tjeneste.domaene.dk/min-tjeneste" }
{ "exp": 1682942294, "iat": 1682941994, "auth_time": 1682941994, "jti": "dc451345-ebb4-4dbd-a6a4-cff8d04839ae", "iss": "https://et-broker.unilogin.dk/auth/realms/broker", "sub": "db829872-f9c0-435f-ac71-9ca38a015fa1", "typ": "Bearer", "azp": "https://tjeneste.domaene.dk/min-tjeneste", "session_state": "938a53ee-ce1f-44e7-86d6-9472c99e77e2", "nonce": "xc12as45gj8723", "acr": "https://data.gov.dk/concept/core/nsis/loa/Low", "scope": "openid", "spec_ver": "OIDC.3.0-UNILOGIN", "unilogin_loa": "ToFaktor", "aktoer_gruppe": "Kontakt", "loa": "3", "uniid": "1000013b36", "client_id": "https://tjeneste.domaene.dk/min-tjeneste" }
1.4 Vedligehold session sikkert med refresh token
Brugerinformation hentes ved at sende access_token med i post-request. For at opretholde en sikker session, anbefales det at invalidere det eksisterende access_token, og få udstedt et nyt efter hvert kald. Dette gøre med et post-request til /token endpointet, med med andre parametre i request body, ved brug af refresh_token fra det tidligere kaldt til /token.
Dette bør også gøres når access_token udløber (expires_in), og inden refresh_token udløber (refresh_expires_in), hvis sessionen skal holdes aktiv.
Eksempel på opdatering af access_token med refresh_token:
httpPost ({ url: "https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/token", body: { grant_type: "refresh_token", client_id: "https://tjeneste.domaene.dk/min-tjeneste", client_secret: "8943b0cc-e9af-11ed-a05b-0242ac120003", refresh_token: "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAi...............buv3TW_v3uxbInNbCh_1StuluCRJWUCY" } })
Til reference kan følgende diagram benyttes, som illustrerer metode til at kalde refresh_token:
1.5 Log ud med OIDC
Logud håndteres af Unilogin brokeren, og kræver derfor kun et get request med redirectUri som parameter. Der arbejdes på at kunne understøtte Frontchannel og Backchannel Logout.
Unilogin stiller krav til at tjenesten understøtter at brugeren kan logge ud.
Til logout get request skal du bruge:
- [REQUIRED] get request url: https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/logout
- [REQUIRED] post_logout_redirect_uri: Den URI, som brugeren sendes hen til
- [REQUIRED] id_token_hint: ID Token fra tidligere request
- [RECOMMENDED] state: Vilkårlig genereret string på minimum 128 bit. Følgende tegn accepteres ikke "{" og "}"
En logud-knap kan således laves simpelt:
<a href="https://et-broker.unilogin.dk/auth/realms/broker/protocol/openid-connect/logout?post_logout_redirect_uri=https://tjeneste.domaene.dk/du-er-nu-logget-ud&id_token_hint=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lk..........ib5jzP0pC7bv-3qZzAc3NcfyvR&state=0123456789fedabe">Log ud</a>
1.6 Eksempel kode WIP
Med udgangspunkt i eksempelværdierne, har vi lavet en eksempel-implementering. Denne tager udgangspunkt i Java Spring-Boot, og implementerer 1 controller samt 2 modeller.