Dette er en teknisk bekrivelse av Token Exchange. For en overordnet beskrivelse se Token Exchange for API-eiere
Token Exchange i HelseID baserer seg på IETF-spesifikasjonen RFC 8693: OAuth 2.0 Token Exchange, som beskriver en protokoll for å hente ut tokens fra en STS for «impersonation» og «delegation».
I HelseID brukes denne spesifikasjonen når bakenforliggende tjenester, i hovedsak REST API-er, skal kalle andre tjenester på vegne av en autentisert person eller virksomhet.
Sentrale begreper
Token Exchange innfører noen nye begreper:
subject_token
: Et access token utstedt av HelseID hvor det er inkludert claims om autentisert person og virksomhet. Dette tokenet veksles inn ved bruk av token exchange.
subject client
: En klient som er mottaker av et subject_token.
actor client
: En klient som ønsker å veksle inn subject_token ved bruk av token exchange for å få et nytt access token som kan brukes til å kalle en annen tjeneste. Denne klienten er typisk et API som må hente data fra et annet API som også er sikret med HelseID.
Protokollflyt
Overordnet flyt
Subject client forespør autentisering av bruker og ber om tilgang til API A.
HelseID utsteder Access Token (AT#1) med tilgang til API A.
Subject client gjør et kall til API A, med AT#1 i Authorization header.
API A trenger data fra API B og trenger dermed et Access Token for dette.
API A gjør en Token Exchange-forespørsel mot HelseID og ber om tilgang til API B.
AT#1 er subject_token i denne forespørselen.
API A er actor client i denne forespørselen.
HelseId utsteder et nytt Access Token (AT#2) og returnerer det til API A.
API A gjør et kall til API B med AT#2 i Authorization header.
Token Exchange forespørsel
grant_type
: alltidurn:ietf:params:oauth:grant-type:token-exchange
.scope
: API resource scopes som ønskes. Separer scopes med mellomrom.subject_token
: Access Token utstedt av HelseID som skal utvekslessubject_token_type
: alltidurn:ietf:params:oauth:token-type:access_token
.client_assertion
: base64 enkodet signert JWT. Se Client Assertion fra actor client.client_assertion_type
: alltidurn:ietf:params:oauth:client-assertion-type:jwt-bearer
.
Alle parametre må sendes i body som en del av en POST request.
Eksempel:
POST /connection/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange& scope=api1 api2& subject_token=[base64 enkodet access token]& subject_token_type=urn:ietf:params:oauth:token-type:access_token& client_assertion=[base64 enkodet jwt]& client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
Respons ved suksess
access_token
: base64 enkodet Access Token. Dette tokenet brukes av actor client ved forespørsler til andre APIer.issued_token_type
: alltidurn:ietf:params:oauth:token-type:access_token
.token_type
: alltidBearer
.expires_in
: levetid i sekunder for returnert token.
Eksempel:
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-cache, no-store { "access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IkI0Q0FFNDUyQzhCNkE4OTNCNkE4NDBBQzhDODRGQjA3MEE0MjZFNDEiLCJ4NXQiOiJ0TXJrVXNpMnFKTzJxRUNzaklUN0J3cENia0UiLCJ0eXAiOiJKV1QifQ. eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM2NiIsIm5iZiI6MTY3MzYwMjc1NSwiaWF0IjoxNjczNjAyNzU1LCJleHAiOjE2NzM2MDYzNTUsImF1ZCI6InVkZWx0OnRlc3QtYXBpIiwic2NvcGUiOlsidWRlbHQ6dGV zdC1hcGkvYXBpIl0sImFtciI6WyJwd2QiXSwiY2xpZW50X2lkIjoidG9rZW5fZXhjaGFuZ2VfYWN0b3JfY2xpZW50IiwiaGVsc2VpZDovL2NsYWltcy9jbGllbnQvb3JpZ2luYWxfY2xpZW50X2lkIjoidG9rZW5fZXhjaG FuZ2Vfc3ViamVjdF9jbGllbnQiLCJjbGllbnRfYW1yIjoicHJpdmF0ZV9rZXlfand0IiwiaGVsc2VpZDovL2NsYWltcy9pZGVudGl0eS9waWQiOiIxNTAzNzEwNDIyOSIsImhlbHNlaWQ6Ly9jbGFpbXMvaWRlbnRpdHkvc 2VjdXJpdHlfbGV2ZWwiOiI0IiwianRpIjoiNzExMzgzRjNENEQ4NjhDMDMzOTdENzEyNTU1N0UzRjAiLCJzaWQiOiI2NzFGOEVCRUU0OEJBRDE0NjgwRUJBNEMwQzI1MDkyMCIsImFjdCI6eyJpc3MiOiJodHRwczovL2xv Y2FsaG9zdDo0NDM2NiIsImNsaWVudF9pZCI6InRva2VuX2V4Y2hhbmdlX2FjdG9yX2NsaWVudCIsImhlbHNlaWQ6Ly9jbGFpbXMvY2xpZW50L2NsYWltcy9vcmducl9wYXJlbnQiOiI5OTk5Nzc3NzQifSwiaGVsc2VpZDo vL2NsYWltcy9jbGllbnQvY2xhaW1zL29yZ25yX3BhcmVudCI6Ijk5OTk3Nzc3NCIsInN1YiI6IlVwVUFpZTNQVTZCYVgyTStTbFZWZVh5cDg2YjRQTXZOeTlpOVppMlNoVWc9IiwiYXV0aF90aW1lIjoxNjczNjAyNzQ1LC JpZHAiOiJ0ZXN0aWRwLW9pZGMiLCJuYW1lIjoiQU5ORSBNQVJLVVNTRU4gRU5HRUJBS0tFTiIsImhlbHNlaWQ6Ly9jbGFpbXMvY2xpZW50L2FtciI6InJzYV9wcml2YXRlX2tleSJ9.BGfYrDi6PLks_dV99SDjCsnKFytc kHIbh0sFVNOWF7Y75ZJVzo4xKf6CpA1AO38VnYk7PsGuM3HpSTQSpXl8IAgJ8CsZ8nTBzRponsneamxBLrcUlMcVq-CeY6NmFyUYvV-FZik7D2spEXzNdTuuPGiK7y2Ik1es0SIW_fdKDAzYi9Y06MhVE9YZJNi1OdJzCSU DGNKg_-2u6D18dX2Gd877VViYm0CL6_4N1LxkaKbxMM8LTwh_7Q36VzbqeQAR9sPJibGuOkdJXlJAAmm2B2WzxEU3OpscWe1zCtQ8jhnqfI9oLcD11nX5C549p_1XiL0DAbZQBQwA9P7ce_frQg", "issued_token_type":"urn:ietf:params:oauth:token-type:access_token", "token_type":"Bearer", "expires_in":3600 }
Respons ved feil
error
: feilkode.error_description
: beskrivelse av feil.
De følgende feilsituasjonene er spesifikke for token exchange:
Feil i validering av subject token:
{ "error": "invalid_request", "error_description": "invalid subject_token - [detaljert informasjon]" }
Dette inkluderer at tokenet ikke er utstedt av HelseID, at det har utgått og annet.
Actor client mangler rettigheter:
{ "error": "invalid_request", "error_description": "not permitted" }
Denne situasjonen oppstår dersom subject client ikke er konfigurert til å tillate token exchange for actor client.
Actor client spør om for brede tillatelser:
{ "error": "invalid_target", "error_description": "invalid scopes requested" }
Dette oppstår dersom klienten spør om scopes som går på tvers av API-ressurser.
Actor client forsøker å veksle inn et token som allerede er vekslet inn for mange ganger:
{ "error": "invalid_request", "error_description": "subject_token exchanged too many times (5)" }
Actor client tilhører en annen Configuration Owner enn
aud
(API ressurs) i subject token:{ "error": "invalid_request", "error_description": "no audience matching configuration owner of client_id [client_id] was found in subject token" }
Eksempel:
HTTP/1.1 400 Bad Request Content-Type: application/json Cache-Control: no-cache, no-store { "error": "invalid_target", "error_description": "invalid scopes requested" }
Client Assertion fra actor client
Client Assertion brukes på samme måte som ved andre flyter.
En Client Assertion er en standard JWT som er bygd opp i henhold til RFC 7523.
Følgende claims må være med:
iss
: client_id til actor client.sub
: client_id til actor clientaud
: HelseID token-endepunkt. Må være liktiss
i subject_token.exp
: tidspunkt client assertion utløper i epoch tid.iat
: tidspunkt for utstedelse av client assertion i epoch tid.
Merk at HelseID stiller krav om at Client Assertions ikke kan ha lenger levetid enn 60 sekunder.
I tillegg til claimene ovenfor støtter HelseID påstander knyttet til identiteten til actor client, spesifikt virksomheten som klienten opererer under. Disse claimene vil reflekteres under act
claimet i Access Token som utstedes ved vellykket Token Exchange.
helseid://client/claims/orgnr_parent
: organisasjonsnummer for hovedenhet fra actor client.helseid://client/claims/orgnr_parent_description
: beskrivelse av organisasjonsnummer for hovedenhet (maks 100 tegn).helseid://client/claims/orgnr_child
: organisasjonsnummer for underenhet fra actor client.helseid://client/claims/orgnr_child_description
: beskrivelse av organisasjonernummer for underenhet (maks 100 tegn).
Eksempel på Client Assertion payload:
{ "exp":1541635665, "iat":"1541635665", "iss":"c26a87dd-b875-412a-a30b-4b487f141153", "sub":"c26a87dd-b875-412a-a30b-4b487f141153", "aud":"https://helseid-sts.test.nhn.no/connect/token" "helseid://client/claims/orgnr_parent":"912159523", "helseid://client/claims/orgnr_description":"EKSEMPEL AS" }
Signering av Client Assertion
Client Assertion må signeres med actor client sin hemmelighet i HelseID. HelseID støtter signering med RSA- og ECDSA-nøkkelpar.
JWT'en signeres med klientens private nøkkel. HelseID bruker deretter den offentlige nøkkelen til å verifisere signaturen og autentisere klienten.
Nøkkelpar settes opp i HelseID Selvbetjening.
Utstedt Access Token
Ved vellykket Token Exchange vil actor client få utstedt et Access Token som med følgende claims:
client_id
: klient identifikator for actor client.
helseid://claims/client/original_client_id
: klient identifikator fra det initielle tokenet som ble utstedt uten Token Exchange (det første subject_tokenet i kjeden).
Claims kopiert fra subject token:
Alle claims med prefiks
helseid://
sub
: HelseID-spesifikk identifikator for autentisert bruker.idp
: IDP som ble brukt i forbindelse med autentisering av bruker.amr
: påloggingsmetode i IDP som ble brukt i forbindelse med autentisering av bruker.auth_time
: tidspunkt brukeren ble autentisert.
Standard JWT claims:
nbf
exp
iss
aud
scope
jti
act
: Et sett av claims som identifiserer virksomhet og klient som opptrer på vegne av en autentisert person. act
claimet er bygd opp som følger:
iss
: HelseID STS sin har identifisert actor client.client_id
: identifikator for actor clienthelseid://claims/client/claims/orgnr_parent
: organisasjonsnummer for hovedenhet.helseid://claims/client/claims/orgnr_parent_description
: beskrivelse av organisasjonsnummer for hovedenhet.helseid://claims/client/claims/orgnr_child
: organisasjonsnummer for underenhet.helseid://claims/client/claims/orgnr_parent_description
: beskrivelse av organisasjosnummer for underenhet.
Eksempel på act claim:
{ "iss": "https://helseid-sts.test.nhn.no", "client_id": "client_actor", "helseid://claims/client/claims/orgnr_parent": "912159523", "helseid://claims/client/claims/orgnr_parent_description": "EKSEMPEL AS" }
For kallkjeder der Token Exchange benyttes flere ganger, vil man få en nøstet struktur av act
claims. Den innerste actor er den eldste, og den ytterste actor er den nyeste - og altså den aktive actor for det aktuelle access token.
Eksempel på act claim med nesting:
{ "iss": "https://helseid-sts.test.nhn.no", "client_id": "client_actor_2", "helseid://claims/client/claims/orgnr_parent": "111111111", "helseid://claims/client/claims/orgnr_parent_description": "NAVN AS", "act": { "iss": "https://helseid-sts.test.nhn.no", "client_id": "client_actor", "helseid://claims/client/claims/orgnr_parent": "912159523", "helseid://claims/client/claims/orgnr_parent_description": "EKSEMPEL AS" } }
I dette eksempelet er client_actor_2
aktiv actor, og client_actor
den forrige.
Eksempel på fullstendig Access Token:
{ "iss": "https://helseid-sts.test.nhn.no", "nbf": 1672911351, "iat": 1672911351, "exp": 1672914951, "aud": "test:test-api", "scope": [ "test:test-api/api" ], "amr": [ "pwd" ], "client_id": "[token_exchange_actor_client_guid]", "helseid://claims/client/original_client_id": "[token_exchange_subject_client_guid]", "client_amr": "private_key_jwt", "helseid://claims/identity/pid": "15037104229", "helseid://claims/identity/security_level": "4", "jti": "2E2F7052528642005005B66995E1D083", "sid": "9CC2BC2A4298DEBA9B0C5AD1BF8EC53B", "act": { "iss": "https://helseid-sts.test.nhn.no", "client_id": "token_exchange_actor_client", "helseid://claims/client/claims/orgnr_parent": "999977774" }, "helseid://claims/client/claims/orgnr_parent": "999977774", "sub": "UpUAie3PU6BaX2M+SlVVeXyp86b4PMvNy9i9Zi2ShUg=", "auth_time": 1672911347, "idp": "testidp-oidc", "name": "ANNE MARKUSSEN ENGEBAKKEN", "helseid://claims/client/amr": "rsa_private_key" }