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.
Following methods that are used to generate access token for user:
/wiki/spaces/PCAB/pages/17452269702
/wiki/spaces/PCAB/pages/17415667759
/wiki/spaces/PCAB/pages/17415798881
/wiki/spaces/PCAB/pages/17415340883
/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.
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
JWT header contains following fields:
Field | Description | Example |
---|
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.
JWT payload contains following fields:
Field | Description | Example |
---|
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).
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 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')