Purpose and requirements Access tokens generation in JWT format is implemented to reduce load of mithril service, that exists on any call to eHealth API that uses Authorization header with access token.
Only access tokens that are used to obtain access to eHealth endpoints (i.e. Create/Update Person Request, Create Declaration Request, Get Person Verification Details etc) can be used in JWT format. Any other tokens that are used in OAuth flow (i.e. authorization_code, refresh_token, session_token) are still generated in existing format.
Access token generation format is controlled by a configuration parameter - ACCESS_TOKEN_JWT .
Existing access token generation format validation must be active and operational.
Access token in JWT format contains only user and client related data, broker data is still validated with api-key validation through Mithril service using existing process, described here MIS authorization
Access tokens in JWT format are not saved to mithril database, as they are only validated on Kong service side.
Implementation Access token in JWT format generation Following methods that are used to generate access token for user:
Exchange oAuth Code Grant to Access Token
https://e-health-ua.atlassian.net/wiki/spaces/PCAB/pages/17415667759
https://e-health-ua.atlassian.net/wiki/spaces/PCAB/pages/17415798881
https://e-health-ua.atlassian.net/wiki/spaces/PCAB/pages/17415340883
https://e-health-ua.atlassian.net/wiki/spaces/PCAB/pages/17416060933
Methods check value of ACCESS_TOKEN_JWT configuration parameter:
true - access tokens for users are generated in JWT format;
false - access tokens for users are generated in opaque format (hashed string of record from mithril database, tokens
table).
Methods responses for generated token are not changed, token is still returned in value
field.
JWT structure Access token in JWT format consist of three base64 encoded strings that are separated by dot symbol:
JWT example (encoded)
eyJhbGciOiJSUzUxMiIsImtpZCI6IlZ1SGVPX3Nyemhua3h6b1RKYzNURW1CYUZHdkhWcXFXR0V5NzRzM3hWak0iLCJ0eXAiOiJKV1QifQ.eyJhY2Nlc3NfdHlwZSI6IkJST0tFUiIsImFwcGxpY2FudF9wZXJzb25faWQiOiJiZjExNDU0Zi1hMmIwLTRhYjQtYjhiZC1jMmFiZjhkYWM0NDAiLCJhcHBsaWNhbnRfdXNlcl9pZCI6IjE1YzcyZTBiLWM2MDktNGViZC1iZmM5LWE0NWQzM2E3M2JjNSIsImF1ZCI6IkVIZWFsdGgiLCJjbGllbnRfaWQiOiI4YTk5ZmZkZi0zMTRlLTQ0MTktOTMxZC1hNzZmNDFmOGM0NTYiLCJleHAiOjE3MTc4NzQzODQsImdyYW50X3R5cGUiOiJwaXNfYXV0aCIsImlhdCI6MTY4NjMxNzQ1OCwiaXNzIjoiRUhlYWx0aCIsImp0aSI6ImQ3NzFmN2M0LTNkZjUtNGRmYy1iNDQ2LTYwMzk1MGU3ZTFhMiIsIm5iZiI6MTY4NjMxNzQ1NywicGVyc29uX2lkIjoiYmYxMTQ1NGYtYTJiMC00YWI0LWI4YmQtYzJhYmY4ZGFjNDQwIiwicmVmcmVzaF90b2tlbiI6ImJucFNORVY2WW10QlVEWmhNM2xxTHpaelUwRjFaejA5Iiwic2NvcGUiOiJhcHA6cmVhZCBwcm9maWxlOnJlYWQiLCJzdWIiOiIxNWM3MmUwYi1jNjA5LTRlYmQtYmZjOS1hNDVkMzNhNzNiYzUiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4ifQ.dxurQjxcv_LQR9YiG7we0vZl8CKd1c2dZ2i8XhDUt7CNxLINFoLgpmcVnUSMf_Slk-YkXOELgT4sjyWkaeoZpWhcWfTeHMNh7KQzMH9Mnn_8_oGGc4yt3ZPYiHxng4KY2BqrLniPYel_FtaZoBKrfpwh6zh7RUdau_irOLIF6C_RTQ7BD-wOyCEeRjc2CchBFk5ZN3Cagwx5RbA_Y__nUpe4nK00HbRENBgDySq4k2lqfkfUUGG7HWMs0r9Ik7neRB1holb0-RrkYFs4NVVkl1N078ryqDhTXoHM8_9Z9FiU9eEE53QGRchVO2Tmo85gynPqfsXVJghykMH56hsvag.eyJhY2Nlc3NfdHlwZSI6IkJST0tFUiIsImFwcGxpY2FudF9wZXJzb25faWQiOiJiZjExNDU0Zi1hMmIwLTRhYjQtYjhiZC1jMmFiZjhkYWM0NDAiLCJhcHBsaWNhbnRfdXNlcl9pZCI6IjE1YzcyZTBiLWM2MDktNGViZC1iZmM5LWE0NWQzM2E3M2JjNSIsImF1ZCI6IkVIZWFsdGgiLCJjbGllbnRfaWQiOiI4YTk5ZmZkZi0zMTRlLTQ0MTktOTMxZC1hNzZmNDFmOGM0NTYiLCJleHAiOjE3MTc4NzQzODQsImdyYW50X3R5cGUiOiJwaXNfYXV0aCIsImlhdCI6MTY4NjMxNzQ1OCwiaXNzIjoiRUhlYWx0aCIsImp0aSI6ImQ3NzFmN2M0LTNkZjUtNGRmYy1iNDQ2LTYwMzk1MGU3ZTFhMiIsIm5iZiI6MTY4NjMxNzQ1NywicGVyc29uX2lkIjoiYmYxMTQ1NGYtYTJiMC00YWI0LWI4YmQtYzJhYmY4ZGFjNDQwIiwicmVmcmVzaF90b2tlbiI6ImJucFNORVY2WW10QlVEWmhNM2xxTHpaelUwRjFaejA5Iiwic2NvcGUiOiJhcHA6cmVhZCBwcm9maWxlOnJlYWQiLCJzdWIiOiIxNWM3MmUwYi1jNjA5LTRlYmQtYmZjOS1hNDVkMzNhNzNiYzUiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4ifQ.dxurQjxcv_LQR9YiG7we0vZl8CKd1c2dZ2i8XhDUt7CNxLINFoLgpmcVnUSMf_Slk-YkXOELgT4sjyWkaeoZpWhcWfTeHMNh7KQzMH9Mnn_8_oGGc4yt3ZPYiHxng4KY2BqrLniPYel_FtaZoBKrfpwh6zh7RUdau_irOLIF6C_RTQ7BD-wOyCEeRjc2CchBFk5ZN3Cagwx5RbA_Y__nUpe4nK00HbRENBgDySq4k2lqfkfUUGG7HWMs0r9Ik7neRB1holb0-RrkYFs4NVVkl1N078ryqDhTXoHM8_9Z9FiU9eEE53QGRchVO2Tmo85gynPqfsXVJghykMH56hsvag
JWT example (decoded)
{
"alg": "RS512",
"kid": "VuHeO_srzhnkxzoTJc3TEmBaFGvHVqqWGEy74s3xVjM",
"typ": "JWT"
}
.
{
"access_type": "BROKER",
"app_id": "ce21628e-317b-4edb-bda6-0de661f24666",
"applicant_person_id": "bf11454f-a2b0-4ab4-b8bd-c2abf8dac440",
"applicant_user_id": "15c72e0b-c609-4ebd-bfc9-a45d33a73bc5",
"aud": "EHealth",
"client_id": "8a99ffdf-314e-4419-931d-a76f41f8c456",
"exp": 1717874384,
"grant_type": "pis_auth",
"iat": 1686317458,
"iss": "EHealth",
"jti": "d771f7c4-3df5-4dfc-b446-603950e7e1a2",
"nbf": 1686317457,
"person_id": "bf11454f-a2b0-4ab4-b8bd-c2abf8dac440",
"redirect_uri": "https://example_uri.com/redirect",
"refresh_token": "bnpSNEV6YmtBUDZhM3lqLzZzU0F1Zz09",
"scope": "app:read profile:read",
"sub": "15c72e0b-c609-4ebd-bfc9-a45d33a73bc5",
"typ": "access_token"
}
.
dxurQjxcv_LQR9YiG7we0vZl8CKd1c2dZ2i8XhDUt7CNxLINFoLgpmcVnUSMf_Slk-YkXOELgT4sjyWkaeoZpWhcWfTeHMNh7KQzMH9Mnn_8_oGGc4yt3ZPYiHxng4KY2BqrLniPYel_FtaZoBKrfpwh6zh7RUdau_irOLIF6C_RTQ7BD-wOyCEeRjc2CchBFk5ZN3Cagwx5RbA_Y__nUpe4nK00HbRENBgDySq4k2lqfkfUUGG7HWMs0r9Ik7neRB1holb0-RrkYFs4NVVkl1N078ryqDhTXoHM8_9Z9FiU9eEE53QGRchVO2Tmo85gynPqfsXVJghykMH56hsvag
Header JWT header contains following fields:
alg
signature or encyption algorithm that is used to generate JWT
RS512
kid
identifier of private key that is used to sign JWT
VuHeO_srzhnkxzoTJc3TEmBaFGvHVqqWGEy74s3xVjM
typ
token type
JWT
Mithril service uses RS512
JWT signature algorithm with JWT
token type.
Payload JWT payload contains following fields:
aud
audiene (who or what the token is intended for)
EHealth
exp
token expiration date and time in unix-format
1714840995
iat
token generation date and time in unix-format
1680685233
nbf
token not valid before date and time in unix-format
1680685232
jti
unique token identifier in uuid format
481aa86b-7bfa-462c-8bcb-1a9e9edff192
sub
subject of token (user_id) in uuid format
9cf6f222-2a6a-4ef2-b7f0-6331e1d706ae
iss
name of token issuer
EHealth
access_type
access type of client, if BROKER - additional api-key validation is needed, if DIRECT - no api-key validation needed
BROKER
app_id
identifier of approval between user, applicant user and client that was used to issue access token
ce21628e-317b-4edb-bda6-0de661f24666
applicant_user_id
identifier of user that initiated access token generation for subject (user) in uuid format
9cf6f222-2a6a-4ef2-b7f0-6331e1d706ae
applicant_person_id
identifier of person in uuid format that is accosiated with user who initiated access token generation for subject (user), if exists
38a72dcc-8cb4-4204-ab3a-913fe1dadb8f
client_id
identifier of client that initiated access token generation for subject (user) in uuid format
8a99ffdf-314e-4419-931d-a76f41f8c456
grant_type
grant type that was used to issue access token, if exists
authorization_code
person_id
identifier of person in uuid format that is accosiated with token subject (user), if exists
38a72dcc-8cb4-4204-ab3a-913fe1dadb8f
redirect_uri
redirect uri that was used by client for access token generation, if exists
https://example_uri.com/redirect
refresh_token
refresh token that was generated with access token
Tm5hRkx2U0hIOUxPMjg5ZlFlK3gzQT09
scope
list of scopes that are allowed by issued access token, separated by space
app:read profile:read
typ
the type of the token
access_token
Mithril service forms payload field based on data that is used for token generation (user_id, client_id, requested scope list, session token, etc).
Signature JWT signature contains signature of token content using algorithm described in alg
field from header.
Mithril service uses asymmetric signature algorithm – token content is signed using private key stored in JWT_PRIVATE_KEY parameter.
Access tokens in JWT format validation Access token in JWT format must be submitted to the same Authorization
header for any method that required access token.
Request with JWT Authorization header (example)
curl --location 'https://api.dev.edenlab.com.ua/api/persons/5865b87e-0da0-4d28-8d3c-e24e0d4813e3/verification' \
--header 'api-key: b1lPV3d3blRHRHdOYVYrYmNPU0tJdz09' \
--header 'Authorization: Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJFSGVhbHRoIiwiZXhwIjoxNjg1NDU4MDkxLCJpYXQiOjE2ODMwMzg4OTEsImlzcyI6IkVIZWFsdGgiLCJqdGkiOiI5OTU2NjVmYS1iYjAyLTQyZWMtYWYyYi1iOWJhMWQyZDkyNDQiLCJuYmYiOjE2ODMwMzg4OTAsInN1YiI6eyJjbGllbnRfaWQiOiI0ZjEyMDUzYi00NTBlLTQyNzUtOWJlYi0yNjU1YjgxNjAxM2YiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9leGFtcGxlNzMuY29tIiwic2NvcGUiOiJkZWNsYXJhdGlvbjpyZWFkIGRlY2xhcmF0aW9uX3JlcXVlc3Q6YXBwcm92ZSBkZWNsYXJhdGlvbl9yZXF1ZXN0OnJlYWQgZGVjbGFyYXRpb25fcmVxdWVzdDpzaWduIGRlY2xhcmF0aW9uX3JlcXVlc3Q6d3JpdGUgZGl2aXNpb246cmVhZCBlbXBsb3llZTpyZWFkIGVtcGxveWVlX3JlcXVlc3Q6YXBwcm92ZSBlbXBsb3llZV9yZXF1ZXN0OnJlamVjdCBlbXBsb3llZV9yZXF1ZXN0OnJlYWQgbGVnYWxfZW50aXR5OnJlYWQgb3RwOnJlYWQgb3RwOndyaXRlIHBlcnNvbjpyZWFkIHBhdGllbnRfc3VtbWFyeTpyZWFkIGVuY291bnRlcjp3cml0ZSBlbmNvdW50ZXI6cmVhZCBlbmNvdW50ZXI6Y2FuY2VsIGVwaXNvZGU6d3JpdGUgZXBpc29kZTpyZWFkIGpvYjpyZWFkIGNvbmRpdGlvbjpyZWFkIGNvbmRpdGlvbjp3cml0ZSBvYnNlcnZhdGlvbjpyZWFkIG9ic2VydmF0aW9uOndyaXRlIGltbXVuaXphdGlvbjpyZWFkIGltbXVuaXphdGlvbjp3cml0ZSBhbGxlcmd5X2ludG9sZXJhbmNlOnJlYWQgYWxsZXJneV9pbnRvbGVyYW5jZTp3cml0ZSBtZWRpY2F0aW9uX3N0YXRlbWVudDpyZWFkIG1lZGljYXRpb25fc3RhdGVtZW50OndyaXRlIGRldmljZTpyZWFkIGRldmljZTp3cml0ZSByaXNrX2Fzc2Vzc21lbnQ6cmVhZCByaXNrX2Fzc2Vzc21lbnQ6d3JpdGUgbWVkaWNhdGlvbl9kaXNwZW5zZTpyZWFkIGRydWdzOnJlYWQgbWVkaWNhdGlvbl9yZXF1ZXN0OmRldGFpbHMgbWVkaWNhdGlvbl9yZXF1ZXN0OnJlYWQgbWVkaWNhdGlvbl9yZXF1ZXN0OnJlamVjdCBtZWRpY2F0aW9uX3JlcXVlc3Q6cmVzZW5kIG1lZGljYXRpb25fcmVxdWVzdF9yZXF1ZXN0OnJlYWQgbWVkaWNhdGlvbl9yZXF1ZXN0X3JlcXVlc3Q6cmVqZWN0IG1lZGljYXRpb25fcmVxdWVzdF9yZXF1ZXN0OnNpZ24gbWVkaWNhdGlvbl9yZXF1ZXN0X3JlcXVlc3Q6d3JpdGUgZGlhZ25vc3RpY19yZXBvcnQ6cmVhZCBkaWFnbm9zdGljX3JlcG9ydDp3cml0ZSBkaWFnbm9zdGljX3JlcG9ydDpjYW5jZWwgcHJvY2VkdXJlOnJlYWQgcHJvY2VkdXJlOndyaXRlIHByb2NlZHVyZTpjYW5jZWwgc2VydmljZV9yZXF1ZXN0Om1ha2VpbnByb2dyZXNzIHNlcnZpY2VfcmVxdWVzdDpjb21wbGV0ZSBzZXJ2aWNlX3JlcXVlc3Q6cmVjYWxsIHNlcnZpY2VfcmVxdWVzdDpjYW5jZWwgc2VydmljZV9yZXF1ZXN0OndyaXRlIHNlcnZpY2VfcmVxdWVzdDpyZWFkIHNlcnZpY2VfcmVxdWVzdDp1c2UgYXBwcm92YWw6Y3JlYXRlIHByb2dyYW1fc2VydmljZTpyZWFkIG1lZGljYXRpb25fYWRtaW5pc3RyYXRpb246cmVhZCBtZWRpY2F0aW9uX2FkbWluaXN0cmF0aW9uOndyaXRlIGhlYWx0aGNhcmVfc2VydmljZTpyZWFkIGVtcGxveWVlX3JvbGU6cmVhZCBwZXJzb25fcmVxdWVzdDp3cml0ZSBwZXJzb25fcmVxdWVzdDpyZWFkIGF1dGhlbnRpY2F0aW9uX21ldGhvZF9yZXF1ZXN0OndyaXRlIGNhcmVfcGxhbjpyZWFkIGFwcHJvdmFsOnJlYWQgYXBwcm92YWw6Y2FuY2VsIGNhcmVfcGxhbjp3cml0ZSBjb21wb3NpdGlvbjpjcmVhdGUgY29tcG9zaXRpb246c2lnbiBjb21wb3NpdGlvbjpjYW5jZWwgY29tcG9zaXRpb246cmVhZCBjb21wb3NpdGlvbjpzZWFyY2ggbGljZW5zZTpyZWFkIGNsaW5pY2FsX2ltcHJlc3Npb246cmVhZCBydWxlX2VuZ2luZV9ydWxlOnJlYWQgbWVkaWNhbF9ldmVudF9jb250ZXh0OnJlYWQgY2xpbmljYWxfaW1wcmVzc2lvbjp3cml0ZSBwZXJzb25fdmVyaWZpY2F0aW9uOmRldGFpbHMgcGVyc29uX3ZlcmlmaWNhdGlvbjp3cml0ZSBwZXJzb25fZW1lcmdlbmN5X2NvbnRhY3Q6cmVhZCIsInVzZXJfaWQiOiIzNWM4YTFhOS05YzkzLTRmMDEtYmNlZS1jNTJjZjU3NjI2YzQifSwidHlwIjoiYWNjZXNzIn0.vfV6CbHmMOisqbUyLqtaau5gxN2UcPwq8rdg2pQLpvAAAT0mZ0PWO9DtveTjArkDi0x5bU5uf8yOKo882zCAuFip1mWOJNhu0qSHr3uMQMCwvwrtY0JGavuf0wA81hlSCah4mE3U5iPBPuPYgfqMiELxxh1-Mz1OsYQsJfS_KTK_8LSW41eBSmcm-3piRXFeDIOiE2REQDG3GNvkXEJ37SCBJTMv8r_ypVcjymRkWk32_ajrqWS17Y66ax-D59Cp2bf1TWNtk8KKIqnIf-g-nG-sSzruoZGtbNzp6mW4yPZkZ-zG5hVpv0u_i50BU8FittPwNLa-EjX3IDqUUrWwpw'
Access tokens in JWT format are validated only by Kong service, if all token validations are passed – scope list from scope
field is used to validate access to requested endpoint.
JWT is validated by following steps:
Validate Authorization header format
in case token parses as JWT – validate it as JWT
else – validate it using existing token format validation (by token verify mithril method)
Verify JWT signature with public key stored in JWT_PUBLIC_KEY and JWT_PUBLIC_KEY_OLD parameters
in case JWT signature is not verified by stored public key - return 401 ('access_denied')
Check token expiry date from exp
field is greater then now()
in case exp
field does not exist or expiry date is in the past - return 401 ('access_denied')
Check token issuer from iss
field equals to ‘EHeath’ value
in case iss
field does not exist or contains any other data - return 401 ('access_denied')
Check token audience from aud field equals to ‘EHealth’ value
in case aud
field does not exist or contains any other data - return 401 ('access_denied')
Check token is not blacklisted in Redis cache database by values of fields from payload (jti, sub, client_id):
check key blacklist_jti_<<jti>> does not exist (for example, blacklist_jti_481aa86b-7bfa-462c-8bcb-1a9e9edff192
);
check key blacklist_user_id_<<sub>> does not exist (for example, blacklist_user_id_481aa86b-7bfa-462c-8bcb-1a9e9edff192
);
check key blacklist_client_id_<<client_id>> does not exist (for example, blacklist_client_id_8a99ffdf-314e-4419-931d-a76f41f8c456
);
check key blacklist_user_id_client_id_<<sub>>_<<client_id>> does not exist (for example, blacklist_user_id_client_id_481aa86b-7bfa-462c-8bcb-1a9e9edff192_8a99ffdf-314e-4419-931d-a76f41f8c456
);
check key blacklist_app_id_<<app_id>> does not exist (for example, blacklist_app_id_ce21628e-317b-4edb-bda6-0de661f24666
);
in case any of the keys exist in Redis cache database - return 401 ('access_denied')