Added methods to parse, verify and get D4Science related info from an OIDC access token (e.g. from bearer authorization header) (#28351)

This commit is contained in:
Mauro Mugnaini 2024-10-25 17:21:07 +02:00
parent 02adc671fd
commit 71d3ffef5f
Signed by: mauro.mugnaini
GPG Key ID: 2440CFD0EB321EA8
12 changed files with 151 additions and 163 deletions

View File

@ -5,4 +5,5 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [v1.0.0-SNAPSHOT]
- Helps on basic retrieve of the AUTHN and AUTHZ form the IAM, hiding the use of the underling implementation. With dome utility helper functions for get the info from the tokens.
- It already support for the use of the custom Keycloak's D4S mappers that maps/shrink the `aud` (and optionally also the resource access) to the value requested via dynamic scope (by default) and `X-D4Science-Context` HTTP header (can be only globally enabled via static flag) (#24701, #23356, #28084).
- Added (deprecated) methods to obtain the token for the user and related tests (#28084)
- Helps to obtain the token for the user and related tests (#28084)
- Helps to parse, verify and get D4Science related info from an OIDC access token (e.g. from bearer authorization header) (#28351)

View File

@ -9,6 +9,8 @@ import org.gcube.common.keycloak.model.AccessToken;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.RefreshToken;
import org.gcube.common.keycloak.model.TokenResponse;
import org.gcube.io.jsonwebtoken.ExpiredJwtException;
import org.gcube.io.jsonwebtoken.security.SignatureException;
public class AbstractIAMResponse implements IAMResponse {
@ -157,6 +159,17 @@ public class AbstractIAMResponse implements IAMResponse {
}
}
@Override
public void verifyAccessToken() throws SignatureException, ExpiredJwtException, D4ScienceIAMClientException {
try {
getIamClient().verifyToken(getAccessTokenString());
} catch (ExpiredJwtException | SignatureException e) {
throw e;
} catch (Exception e) {
throw new D4ScienceIAMClientException(e);
}
}
@Override
public boolean isRefreshTokenValid() throws D4ScienceIAMClientException {
return isRefreshTokenValid(true);
@ -174,4 +187,18 @@ public class AbstractIAMResponse implements IAMResponse {
throw new D4ScienceIAMClientException(e);
}
}
@Override
public void verifyRefreshToken() throws SignatureException, ExpiredJwtException, D4ScienceIAMClientException {
String refreshTokenString = getRefreshTokenString();
if (refreshTokenString == null) {
try {
getIamClient().verifyToken(refreshTokenString);
} catch (ExpiredJwtException | SignatureException e) {
throw e;
} catch (Exception e) {
throw new D4ScienceIAMClientException(e);
}
}
}
}

View File

@ -237,4 +237,5 @@ public class D4ScienceIAMClient {
PublishedRealmRepresentation realmInfo = keycloakClient.getRealmInfo(realmBaseURL);
ModelUtils.verify(token, realmInfo.getPublicKey());
}
}

View File

@ -3,6 +3,8 @@ package org.gcube.common.iam;
import java.util.Set;
import org.gcube.common.keycloak.model.AccessToken;
import org.gcube.io.jsonwebtoken.ExpiredJwtException;
import org.gcube.io.jsonwebtoken.security.SignatureException;
public interface IAMResponse {
@ -90,14 +92,14 @@ public interface IAMResponse {
String getName() throws D4ScienceIAMClientException;
/**
* Checks if the access token is valid by checking the digital signature and the token expiration
* Quick way to check if the access token is valid by checking the digital signature and the token expiration
* @return <code>true</code> if the access token is valid, <code>false</code> otherwise
* @throws D4ScienceIAMClientException if something goes wrong during the token validity checks
*/
boolean isAccessTokenValid() throws D4ScienceIAMClientException;
/**
* Checks if the access token is valid by checking the digital signature and the token expiration if the <code>checkExpiration</code> parameter is <code>true</code>
* Quick way to check if the access token is valid by checking the digital signature and the token expiration if the <code>checkExpiration</code> parameter is <code>true</code>
* @param checkExpiration checks also if the token is expired
* @return <code>true</code> if the access token is valid, <code>false</code> otherwise
* @throws D4ScienceIAMClientException if something goes wrong during the token validity checks
@ -105,14 +107,22 @@ public interface IAMResponse {
boolean isAccessTokenValid(boolean checkExpiration) throws D4ScienceIAMClientException;
/**
* Checks if the refresh token present in the current response and it is valid by checking the digital signature and the token expiration
* Verifies the access token integrity and validity; token digital signature and expiration are reported via specific exceptions.
* @throws SignatureException if the token has been tampered and/or signature is invalid
* @throws ExpiredJwtException if the token validity is expired
* @throws D4ScienceIAMClientException if something else goes wrong during the token verification
*/
void verifyAccessToken() throws SignatureException, ExpiredJwtException, D4ScienceIAMClientException;
/**
* Quick way to check if the refresh token present in the current response and it is valid by checking the digital signature and the token expiration
* @return <code>true</code> if the refresh token is valid, <code>false</code> otherwise
* @throws D4ScienceIAMClientException if something goes wrong during the token validity checks
*/
boolean isRefreshTokenValid() throws D4ScienceIAMClientException;
/**
* Checks if the refresh token present in the current response and it is valid by checking the digital signature and the token
* Quick way to check if the refresh token present in the current response and it is valid by checking the digital signature and the token
* expiration if the <code>checkExpiration</code> parameter is <code>true</code>
* @param checkExpiration checks also if the token is expired
* @return <code>true</code> if the refresh token is valid, <code>false</code> otherwise
@ -120,4 +130,12 @@ public interface IAMResponse {
*/
boolean isRefreshTokenValid(boolean checkExpiration) throws D4ScienceIAMClientException;
/**
* Verifies the refresh token integrity and validity; token digital signature and expiration are reported via specific exceptions.
* @throws SignatureException if the token has been tampered and/or signature is invalid
* @throws ExpiredJwtException if the token validity is expired
* @throws D4ScienceIAMClientException if something else goes wrong during the token verification
*/
void verifyRefreshToken() throws SignatureException, ExpiredJwtException, D4ScienceIAMClientException;
}

View File

@ -0,0 +1,50 @@
package org.gcube.common.iam;
import java.net.URL;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.TokenResponse;
public class OIDCBearerAuth extends AbstractIAMResponse {
protected OIDCBearerAuth(D4ScienceIAMClient iamClient, TokenResponse tokenResponse) {
super(iamClient, tokenResponse);
}
/**
* Constructs a new object from an HTTP authorization header containing the bearer token with an OIDC access-token.
* @param authorizationHeader the HTTP authorization header
* @return the auth object
*/
public static OIDCBearerAuth fromAuthorizationHeader(String authorizationHeader) {
return fromBearerAuthorization(authorizationHeader);
}
/**
* Constructs a new object from a bearer token with an OIDC access-token.
* @param bearerAuthorization the bearer token (with or without 'bearer ' prefix
* @return the auth object
*/
public static OIDCBearerAuth fromBearerAuthorization(String bearerAuthorization) {
return fromAccessTokenString(bearerAuthorization.replace("bearer ", ""));
}
/**
* Constructs a new object from an OIDC base64 encoded access-token string.
* @param accessToken the OIDC base64 encoded access-token string
* @return the auth object
*/
public static OIDCBearerAuth fromAccessTokenString(String accessToken) {
TokenResponse tr = new TokenResponse();
tr.setAccessToken(accessToken);
try {
return new OIDCBearerAuth(
D4ScienceIAMClient.newInstance(new URL(ModelUtils.getAccessTokenFrom(tr).getIssuer())),
tr);
} catch (Exception e) {
// This case is almost impossible if the access token is parse correctly and contains the mandatory `iss` claim
return null;
}
}
}

View File

@ -1,12 +1,17 @@
package org.gcube.common.iam;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Set;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.io.jsonwebtoken.ExpiredJwtException;
import org.gcube.io.jsonwebtoken.security.SignatureException;
import org.junit.After;
import org.junit.FixMethodOrder;
import org.junit.Test;
@ -291,4 +296,47 @@ public class TestD4ScienceIAMClient {
assertTrue("Realm roles are not as expected.", authn.getGlobalRoles().containsAll(USER_REALM_ROLES));
assertTrue("The authn access token is not valid.", authn.isAccessTokenValid());
}
@Test(expected = ExpiredJwtException.class)
public void test7OIDCBearerAuth() throws Exception {
String bearer = "";
try (InputStreamReader isr = new InputStreamReader(
getClass().getClassLoader().getResourceAsStream("access-token-b64-encoded.txt"))) {
bearer = "bearer " + new BufferedReader(isr).readLine();
}
OIDCBearerAuth auth = OIDCBearerAuth.fromBearerAuthorization(bearer);
logger.info("Authn scope: {}", auth.getAccessToken().getScope());
logger.info("Authn AUDIENCE: {}", (Object[]) auth.getAccessToken().getAudience());
logger.info("Authn RESOURCE ACCESS: {}", auth.getAccessToken().getResourceAccess());
logger.info("Authn ALL roles: {}", auth.getRoles());
logger.info("Authn REALM roles: {}", auth.getGlobalRoles());
logger.info("Authn AUDIENCE's roles: {}", auth.getContextRoles());
logger.info("Authn D4S identity: '{}' by {} [{}]", auth.getName(), auth.getContactPerson(),
auth.getContactOrganization());
logger.info("Authn access token:\n{}",
ModelUtils.getAccessTokenPayloadJSONStringFrom(auth.getTokenResponse()));
assertEquals("Realm roles are not as expected.", CLIENT_REALM_ROLES, auth.getGlobalRoles());
assertEquals("Name is not the expected.", NAME, auth.getName());
assertEquals("Contact person is not the expected.", CONTACT_PERSON, auth.getContactPerson());
assertEquals("Contat organization is not the expected.", CONTACT_ORGANIZATION, auth.getContactOrganization());
assertFalse("The authn access token is not valid.", auth.isAccessTokenValid());
try {
auth.verifyAccessToken();
} catch (ExpiredJwtException e) {
// This is OK in the test
logger.info("Token is correclty expired");
throw e;
} catch (SignatureException e) {
// This is not OK in the test
logger.error("Token signature is invalid", e);
throw e;
} catch (D4ScienceIAMClientException e) {
logger.error("Somethig else has gone wrong in token verification", e);
throw e;
}
}
}

View File

@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSSklZNEpoNF9qdDdvNmREY0NlUDFfS1l0akcxVExXVW9oMkQ2Tzk1bFNBIn0.eyJleHAiOjE3Mjk4NTg5MTksImlhdCI6MTcyOTg1NTMxOSwianRpIjoiMDFmZjRkMWEtNWVkYy00ZTllLWEwOTMtZTRjZDFhYTM1YjZlIiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5kZXYuZDRzY2llbmNlLm9yZy9hdXRoL3JlYWxtcy9kNHNjaWVuY2UiLCJhdWQiOlsiY29uZHVjdG9yLXNlcnZlciIsIiUyRmdjdWJlJTJGZGV2c2VjJTJGQ0NQIl0sInN1YiI6IjQ4NmRhMDYwLTM0NmYtNDZiOS1iOTliLTMyMmIwNGJkMzE3MiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImtleWNsb2FrLWNsaWVudC11bml0LXRlc3QiLCJzZXNzaW9uX3N0YXRlIjoiM2EzMTUxMmItODA4ZC00MGYzLTg5YTQtNjgyZDMzZmRkZmI2IiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImQ0cy1jbGllbnQiXX0sInJlc291cmNlX2FjY2VzcyI6eyJjb25kdWN0b3Itc2VydmVyIjp7InJvbGVzIjpbImR1bW15LXRlc3Qtcm9sZSJdfSwia2V5Y2xvYWstY2xpZW50LXVuaXQtdGVzdCI6eyJyb2xlcyI6WyJ1bWFfcHJvdGVjdGlvbiJdfSwiJTJGZ2N1YmUlMkZkZXZzZWMlMkZDQ1AiOnsicm9sZXMiOlsiTWVtYmVyIl19fSwic2NvcGUiOiJkNHMtaWRlbnRpdHkgcHJvZmlsZSBlbWFpbCIsInNpZCI6IjNhMzE1MTJiLTgwOGQtNDBmMy04OWE0LTY4MmQzM2ZkZGZiNiIsImNsaWVudElkIjoia2V5Y2xvYWstY2xpZW50LXVuaXQtdGVzdCIsImNsaWVudEhvc3QiOiI5My42Ni4xODUuNzUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNvbnRhY3RfcGVyc29uIjoibWF1cm8ubXVnbmFpbmkiLCJjb250YWN0X29yZ2FuaXNhdGlvbiI6Ik51Ymlzd2FyZSBTLnIubC4iLCJuYW1lIjoiS2V5Y2xvYWstY2xpZW50IHVuaXQtdGVzdCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1rZXljbG9hay1jbGllbnQtdW5pdC10ZXN0IiwiY2xpZW50QWRkcmVzcyI6IjkzLjY2LjE4NS43NSJ9.XqoBskow4LYbNum8p7XTgWFfElH8CCxVdZSzVbeN7LYhhMvk20D8qi4Mj0c4WXoyXH94JWSH8dzeh9EY2SCmEVPub_YxDYo0lW96BOtWSw87X-BPQM92Lw52LT3NicsJA6zGTefWLJvPI-YDuDTs31x8cZuc0aawJcwXDFyfNJWA0DAdhodcS5U9_mXItYSQtOT2Ii4s78NGKsRfvA-PD88m79xY2gh2fAUK9Tf4GyRoSqHb_lrIqG3fnVQaKwi9Sx01Z0MqXBzpYVBXvao8k4UIJlNa775ktjmQ8VvYJwVsDyU5KlYxS21d1Zutwa8Rq0vTws4FtrEwGS3nwLPcHg

View File

@ -1,40 +0,0 @@
{
"exp": 1622216161,
"iat": 1622215861,
"jti": "d391d814-a1f6-4a40-9aa3-3f51ec59988c",
"iss": "https://accounts.dev.d4science.org/auth/realms/d4science",
"aud": "account",
"sub": "1c84108a-201d-4e20-8ad2-d72b08d58f8a",
"typ": "Bearer",
"azp": "lr62_portal",
"session_state": "1a054bb7-4d87-44a9-ad1f-1746ef2dd522",
"acr": "1",
"allowed-origins": [
"https://dev.d4science.org"
],
"realm_access": {
"roles": [
"offline_access",
"Infrastructure-Client",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "email profile",
"email_verified": true,
"name": "Gino Stilla",
"groups": [],
"preferred_username": "gino.stilla",
"given_name": "Gino",
"locale": "it",
"family_name": "Stilla",
"email": "gino@stilla.com"
}

View File

@ -1,10 +0,0 @@
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSSklZNEpoNF9qdDdvNmREY0NlUDFfS1l0akcxVExXVW9oMkQ2Tzk1bFNBIn0.eyJleHAiOjE2MjIyMTYxNjEsImlhdCI6MTYyMjIxNTg2MSwianRpIjoiZDM5MWQ4MTQtYTFmNi00YTQwLTlhYTMtM2Y1MWVjNTk5ODhjIiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5kZXYuZDRzY2llbmNlLm9yZy9hdXRoL3JlYWxtcy9kNHNjaWVuY2UiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMWM4NDEwOGEtMjAxZC00ZTIwLThhZDItZDcyYjA4ZDU4ZjhhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoibHI2Ml9wb3J0YWwiLCJzZXNzaW9uX3N0YXRlIjoiMWEwNTRiYjctNGQ4Ny00NGE5LWFkMWYtMTc0NmVmMmRkNTIyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwczovL2Rldi5kNHNjaWVuY2Uub3JnIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsIkluZnJhc3RydWN0dXJlLUNsaWVudCIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJHaW5vIFN0aWxsYSIsImdyb3VwcyI6W10sInByZWZlcnJlZF91c2VybmFtZSI6Imdpbm8uc3RpbGxhIiwiZ2l2ZW5fbmFtZSI6Ikdpbm8iLCJsb2NhbGUiOiJpdCIsImZhbWlseV9uYW1lIjoiU3RpbGxhIiwiZW1haWwiOiJnaW5vQHN0aWxsYS5jb20ifQ.C43CAMgoHFhRNPACXPKDr_b1ytZeYeB2_AxTOl0jhG5YUpzoigtjwdrYptJbDtlO0fO3Ex9-KgKKBpUROMb0tC7YjuVgK6uGmaBcXGvA2S9mMLVlpl8u0KWJrrvzjPSSBHqH1fKZ6RHhZYkukMAeEeN5nT5SJoftiBNfnQi0wdjsN6fWUDLVQ3kYFQ_8C2RuO-yivSc9TyVpV-1M6ij7PEplWf2UjoygJKchs9R6x_sLHbaQHPTE24PMEY7GcEsgUwBXR3bkcWZ9cVuxAnbcIYITT-qC6V4YXodS2cYew3WoSaMl8LfTmIl7oFiv3lIDYvQ3dd-X8h1QkkbTPlVLOQ",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjOTk5YmVjNC1iNDc4LTQ4Y2YtYmI5OS0wMWMxODY5NzcwNGIifQ.eyJleHAiOjE2MjIyMTc2NjEsImlhdCI6MTYyMjIxNTg2MSwianRpIjoiNzA2YzEyZjUtZDk3OS00MjVmLWI1NzctMzgxNWU4ZTdlNWM2IiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5kZXYuZDRzY2llbmNlLm9yZy9hdXRoL3JlYWxtcy9kNHNjaWVuY2UiLCJhdWQiOiJodHRwczovL2FjY291bnRzLmRldi5kNHNjaWVuY2Uub3JnL2F1dGgvcmVhbG1zL2Q0c2NpZW5jZSIsInN1YiI6IjFjODQxMDhhLTIwMWQtNGUyMC04YWQyLWQ3MmIwOGQ1OGY4YSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJscjYyX3BvcnRhbCIsInNlc3Npb25fc3RhdGUiOiIxYTA1NGJiNy00ZDg3LTQ0YTktYWQxZi0xNzQ2ZWYyZGQ1MjIiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUifQ.2nYaWSEIbzr56vKx39AxomfiWoSQweAnepf7p3maZMs",
"token_type": "bearer",
"not-before-policy": 1618317421,
"session_state": "1a054bb7-4d87-44a9-ad1f-1746ef2dd522",
"scope": "email profile"
}

View File

@ -1,63 +0,0 @@
{
"exp": 1621960710,
"iat": 1621960410,
"jti": "5a2a2240-8a32-40c9-8cc2-456dd8b089d9",
"iss": "https://accounts.dev.d4science.org/auth/realms/d4science",
"aud": "conductor-server",
"sub": "a47dfe16-b4ed-44ed-a1d9-97ecd504360c",
"typ": "Bearer",
"azp": "keycloak-client",
"session_state": "1550e4ef-5a92-430d-aa0f-242e5f8048de",
"acr": "1",
"realm_access": {
"roles": [
"offline_access",
"Infrastructure-Client",
"uma_authorization"
]
},
"resource_access": {
"keycloak-client": {
"roles": [
"uma_protection"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"authorization": {
"permissions": [
{
"scopes": [
"get"
],
"rsid": "249fd469-79c5-4b85-b195-f29b3eb60345",
"rsname": "metadata"
},
{
"scopes": [
"get",
"start",
"terminate"
],
"rsid": "a6f3eade-7404-4e5d-9070-800adb5aac4e",
"rsname": "workflow"
},
{
"rsid": "1b6c00b7-9139-4eaa-aac7-20231fee05a5",
"rsname": "Default Resource"
}
]
},
"scope": "email profile",
"clientId": "keycloak-client",
"clientHost": "2.231.31.240",
"email_verified": false,
"preferred_username": "service-account-keycloak-client",
"clientAddress": "2.231.31.240"
}

View File

@ -1,36 +0,0 @@
{
"exp": 1621962210,
"iat": 1621960410,
"jti": "ca223961-22a2-4171-af3e-f109749e83ea",
"iss": "https://accounts.dev.d4science.org/auth/realms/d4science",
"aud": "https://accounts.dev.d4science.org/auth/realms/d4science",
"sub": "a47dfe16-b4ed-44ed-a1d9-97ecd504360c",
"typ": "Refresh",
"azp": "keycloak-client",
"session_state": "1550e4ef-5a92-430d-aa0f-242e5f8048de",
"authorization": {
"permissions": [
{
"scopes": [
"get"
],
"rsid": "249fd469-79c5-4b85-b195-f29b3eb60345",
"rsname": "metadata"
},
{
"scopes": [
"get",
"start",
"terminate"
],
"rsid": "a6f3eade-7404-4e5d-9070-800adb5aac4e",
"rsname": "workflow"
},
{
"rsid": "1b6c00b7-9139-4eaa-aac7-20231fee05a5",
"rsname": "Default Resource"
}
]
},
"scope": "email profile"
}

View File

@ -1,9 +0,0 @@
{
"upgraded": false,
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSSklZNEpoNF9qdDdvNmREY0NlUDFfS1l0akcxVExXVW9oMkQ2Tzk1bFNBIn0.eyJleHAiOjE2MjE5NjA3MTAsImlhdCI6MTYyMTk2MDQxMCwianRpIjoiNWEyYTIyNDAtOGEzMi00MGM5LThjYzItNDU2ZGQ4YjA4OWQ5IiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5kZXYuZDRzY2llbmNlLm9yZy9hdXRoL3JlYWxtcy9kNHNjaWVuY2UiLCJhdWQiOiJjb25kdWN0b3Itc2VydmVyIiwic3ViIjoiYTQ3ZGZlMTYtYjRlZC00NGVkLWExZDktOTdlY2Q1MDQzNjBjIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoia2V5Y2xvYWstY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjE1NTBlNGVmLTVhOTItNDMwZC1hYTBmLTI0MmU1ZjgwNDhkZSIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJJbmZyYXN0cnVjdHVyZS1DbGllbnQiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImtleWNsb2FrLWNsaWVudCI6eyJyb2xlcyI6WyJ1bWFfcHJvdGVjdGlvbiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwiYXV0aG9yaXphdGlvbiI6eyJwZXJtaXNzaW9ucyI6W3sic2NvcGVzIjpbImdldCJdLCJyc2lkIjoiMjQ5ZmQ0NjktNzljNS00Yjg1LWIxOTUtZjI5YjNlYjYwMzQ1IiwicnNuYW1lIjoibWV0YWRhdGEifSx7InNjb3BlcyI6WyJnZXQiLCJzdGFydCIsInRlcm1pbmF0ZSJdLCJyc2lkIjoiYTZmM2VhZGUtNzQwNC00ZTVkLTkwNzAtODAwYWRiNWFhYzRlIiwicnNuYW1lIjoid29ya2Zsb3cifSx7InJzaWQiOiIxYjZjMDBiNy05MTM5LTRlYWEtYWFjNy0yMDIzMWZlZTA1YTUiLCJyc25hbWUiOiJEZWZhdWx0IFJlc291cmNlIn1dfSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJrZXljbG9hay1jbGllbnQiLCJjbGllbnRIb3N0IjoiMi4yMzEuMzEuMjQwIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQta2V5Y2xvYWstY2xpZW50IiwiY2xpZW50QWRkcmVzcyI6IjIuMjMxLjMxLjI0MCJ9.UKcREwcaJc9tpfUIsIfqbN-uON1lrtAcVQSoZan29hyQ-t8o6tjWS4-ix8JnWN8YBxU0Gbo1XcGx2NEnX7QCcAt9R46I9jpd5D9LBF-DF1G5zTVc1Cwm9-XcQ9vU_KDJ_qOzhcbPe1ZeAkYV4LpRXuPS7bBSUiNYExHoWBQTUTjNUc7rJRGWk14YKNjEgvri46RZw3ZZQ19JdjktyLz4WNGF8asSAmLXTeJ4q7O1kWttDzxjiz6QMW1378lYCb_GfXWsnAWbm7zpfz2-Fs3NmZO35BUw_jba_l_8Uog35X9qhsgcw2-_sWEB0vGLEHvz2zowpy70zjpoeHZYq6LeBw",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjOTk5YmVjNC1iNDc4LTQ4Y2YtYmI5OS0wMWMxODY5NzcwNGIifQ.eyJleHAiOjE2MjE5NjIyMTAsImlhdCI6MTYyMTk2MDQxMCwianRpIjoiY2EyMjM5NjEtMjJhMi00MTcxLWFmM2UtZjEwOTc0OWU4M2VhIiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5kZXYuZDRzY2llbmNlLm9yZy9hdXRoL3JlYWxtcy9kNHNjaWVuY2UiLCJhdWQiOiJodHRwczovL2FjY291bnRzLmRldi5kNHNjaWVuY2Uub3JnL2F1dGgvcmVhbG1zL2Q0c2NpZW5jZSIsInN1YiI6ImE0N2RmZTE2LWI0ZWQtNDRlZC1hMWQ5LTk3ZWNkNTA0MzYwYyIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJrZXljbG9hay1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiMTU1MGU0ZWYtNWE5Mi00MzBkLWFhMGYtMjQyZTVmODA0OGRlIiwiYXV0aG9yaXphdGlvbiI6eyJwZXJtaXNzaW9ucyI6W3sic2NvcGVzIjpbImdldCJdLCJyc2lkIjoiMjQ5ZmQ0NjktNzljNS00Yjg1LWIxOTUtZjI5YjNlYjYwMzQ1IiwicnNuYW1lIjoibWV0YWRhdGEifSx7InNjb3BlcyI6WyJnZXQiLCJzdGFydCIsInRlcm1pbmF0ZSJdLCJyc2lkIjoiYTZmM2VhZGUtNzQwNC00ZTVkLTkwNzAtODAwYWRiNWFhYzRlIiwicnNuYW1lIjoid29ya2Zsb3cifSx7InJzaWQiOiIxYjZjMDBiNy05MTM5LTRlYWEtYWFjNy0yMDIzMWZlZTA1YTUiLCJyc25hbWUiOiJEZWZhdWx0IFJlc291cmNlIn1dfSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIn0.63dE64hNYpxQRV-M5zOrLLWt9cehJI4DcIbHia977r4",
"token_type": "Bearer",
"not-before-policy": 1618317421
}