Skip to content

Retain access token claim

Madhumita Subramaniam edited this page Aug 1, 2023 · 10 revisions

Reference - https://support.gluu.org/access-management/9401/access-token-introspection-script-how-to-retain-claim/

Create client :

  • with access token as jwt ( accessTokenAsJwt=true )
  • invoke introspection script during access token creation (runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims=true)
  • Add introspection script and add it to client (otherwise it will not be invoked)

Client LDIF

dn: inum=0008-525a95a3-5fe1-4ecf-878c-06f438e3f500,ou=clients,o=gluu
objectClass: oxAuthClient
objectClass: top
del: false
displayName: Retain Claims Client
inum: 0008-525a95a3-5fe1-4ecf-878c-06f438e3f500
oxAccessTokenAsJwt: true
oxAttributes:: ew0KICAidGxzQ2xpZW50QXV0aFN1YmplY3REbiI6IG51bGwsDQogICJydW5Jb
 nRyb3NwZWN0aW9uU2NyaXB0QmVmb3JlQWNjZXNzVG9rZW5Bc0p3dENyZWF0aW9uQW5kSW5jbHVk
 ZUNsYWltcyI6IHRydWUsDQogICJrZWVwQ2xpZW50QXV0aG9yaXphdGlvbkFmdGVyRXhwaXJhdGl
 vbiI6IGZhbHNlLA0KICAiYWxsb3dTcG9udGFuZW91c1Njb3BlcyI6IGZhbHNlLA0KICAic3Bvbn
 RhbmVvdXNTY29wZXMiOiBbDQogICAgDQogIF0sDQogICJzcG9udGFuZW91c1Njb3BlU2NyaXB0R
 G5zIjogWw0KICAgIA0KICBdLA0KICAiYmFja2NoYW5uZWxMb2dvdXRVcmkiOiBbDQogICAgDQog
 IF0sDQogICJiYWNrY2hhbm5lbExvZ291dFNlc3Npb25SZXF1aXJlZCI6IGZhbHNlLA0KICAiYWR
 kaXRpb25hbEF1ZGllbmNlIjogWw0KICAgIA0KICBdLA0KICAicG9zdEF1dGhuU2NyaXB0cyI6IF
 sNCiAgICAiaW51bT1BNTFELkY3MkEsb3U9c2NyaXB0cyxvPWdsdXUiDQogIF0sDQogICJjb25zZ
 W50R2F0aGVyaW5nU2NyaXB0cyI6IFsNCiAgICANCiAgXSwNCiAgImludHJvc3BlY3Rpb25TY3Jp
 cHRzIjogWw0KICAgICAiaW51bT0yREFGLUFBOTAsb3U9c2NyaXB0cyxvPWdsdXUiDQogIF0sDQo
 gICJycHRDbGFpbXNTY3JpcHRzIjogWw0KICAgIA0KICBdDQp9
oxAuthAppType: web
oxAuthBackchannelUserCodeParameter: false
oxAuthClientSecret: dApfwy7qGfBIGHf8802DoQ==
oxAuthGrantType: authorization_code
oxAuthGrantType: client_credentials
oxAuthGrantType: implicit
oxAuthGrantType: refresh_token
oxAuthIdTokenSignedResponseAlg: HS256
oxAuthLogoutSessionRequired: false
oxAuthLogoutURI: https://ce-dev5.gluu.org/identity/ssologout.htm
oxAuthPostLogoutRedirectURI: https://ce-dev5.gluu.org/identity/finishlogout.
 htm
oxAuthRedirectURI: https://ce-dev5.gluu.org/identity/authcode.htm
oxAuthRedirectURI: https://ce-dev5.gluu.org/identity/scim/auth
oxAuthRedirectURI: https://ce-dev5.gluu.org/oxauth-rp/home.htm
oxAuthRedirectURI: https://client.example.com/cb
oxAuthRequireAuthTime: false
oxAuthResponseType: code
oxAuthResponseType: id_token
oxAuthScope: inum=10B2,ou=scopes,o=gluu
oxAuthScope: inum=6D99,ou=scopes,o=gluu
oxAuthScope: inum=764C,ou=scopes,o=gluu
oxAuthScope: inum=F0C4,ou=scopes,o=gluu
oxAuthSubjectType: public
oxAuthTokenEndpointAuthMethod: client_secret_basic
oxAuthTrustedClient: false
oxClaimRedirectURI: https://ce-dev5.gluu.org/oxauth/restv1/uma/gather_claims
oxDisabled: false
oxIncludeClaimsInIdToken: false
oxLastAccessTime: 20201125150523.157Z
oxLastLogonTime: 20201125150523.157Z
oxPersistClientAuthorizations: false
oxRptAsJwt: false

Introspection script

Allows to persist data with refresh token and then pass it to access token during refreshing.

# oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
# Copyright (c) 2021, Gluu
#
# Author: Yuriy Zabrovarnyy
#
#

from org.gluu.model.custom.script.type.introspection import IntrospectionType
from java.lang import String
from org.gluu.oxauth.model.common import AuthorizationGrantList
from org.gluu.service.cdi.util import CdiUtil
from org.gluu.oxauth.service import GrantService
from org.gluu.oxauth.model.ldap import TokenType
from org.gluu.oxauth.model.ldap import TokenLdap

class Introspection(IntrospectionType):
    def __init__(self, currentTimeMillis):
        self.currentTimeMillis = currentTimeMillis

    def init(self, configurationAttributes):
        print "Introspection script (retain claims). Initializing ..."
        print "Introspection script (retain claims). Initialized successfully"

        return True

    def destroy(self, configurationAttributes):
        print "Introspection script (retain claims). Destroying ..."
        print "Introspection script (retain claims). Destroyed successfully"
        return True

    def getApiVersion(self):
        return 1

    # Returns boolean, true - apply introspection method, false - ignore it.
    # This method is called after introspection response is ready. This method can modify introspection response.
    # Note :
    # responseAsJsonObject - is org.codehaus.jettison.json.JSONObject, you can use any method to manipulate json
    # context is reference of org.gluu.oxauth.service.external.context.ExternalIntrospectionContext (in https://github.com/GluuFederation/oxauth project, )
    def modifyResponse(self, responseAsJsonObject, context):
        print "modifyResponse invoked"

        authorizationGrantList = CdiUtil.bean(AuthorizationGrantList)
        grantService = CdiUtil.bean(GrantService)

        refreshToken = context.getHttpRequest().getParameter("refresh_token")
        if refreshToken is None:
            print "No refresh token parameter. Put original claim - claim1=value1"
            responseAsJsonObject.accumulate("claim1", "value1") # AT1

            # save it also in refresh token
            grants = grantService.getGrantsByGrantId(context.getTokenGrant().getGrantId())
            RT1 = {}
            for grant in grants:
                if (grant.getTokenTypeEnum() == TokenType.REFRESH_TOKEN):
                    RT1 = grant

                    print "RT1 hashed code: " + RT1.getTokenCode()
                    RT1.getAttributes().getAttributes().put("claim1", "value1")
                    grantService.mergeSilently(RT1)

                    return True

        responseAsJsonObject.accumulate("refresh_token", refreshToken)
        print "Refresh token: " + refreshToken

        clientId = context.getTokenGrant().getClientId()
        print "ClientId: " + clientId

        grantId = authorizationGrantList.getAuthorizationGrantByRefreshToken(clientId, refreshToken).getGrantId()
        print "grantId: " + grantId
        responseAsJsonObject.accumulate("grant_id", grantId)

        grants = grantService.getGrantsByGrantId(grantId)

        RT = {}
        for grant in grants:
            if (grant.getTokenTypeEnum() == TokenType.REFRESH_TOKEN):
                RT = grant
        print "RT hashed code: " + RT.getTokenCode()


        valueFromAT = RT.getAttributes().getAttributes().get("claim1")
        print "valueFromAT: " + valueFromAT
        responseAsJsonObject.accumulate("claim1", valueFromAT)

        return True


/opt/gluu/jetty/oxauth/logs/oxauth_script.log

(PythonService.java:243) - modifyResponse invoked
(PythonService.java:243) - No refresh token parameter. Put original claim - claim1=value1
(PythonService.java:243) - RT1 hashed code: 3e5aa58c15cf5c382dce9033c0a2d9e1164dc4bbaf557c4cf512f26ddfba321e
(PythonService.java:243) - modifyResponse invoked
(PythonService.java:243) - Refresh token: 5a80dffa-f96b-4b62-8f5b-c933b10cf513
(PythonService.java:243) - ClientId: 0008-525a95a3-5fe1-4ecf-878c-06f438e3f500
(PythonService.java:243) - grantId: 97650fac-aa45-4068-a35b-bf5aee261d57
(PythonService.java:243) - RT hashed code: 3e5aa58c15cf5c382dce9033c0a2d9e1164dc4bbaf557c4cf512f26ddfba321e
(PythonService.java:243) - valueFromAT: value1

Client test

@Parameters({"userId", "userSecret", "redirectUri"})
    @Test(enabled = false) // retain claims script has to be enabled and client pre-configured (not avaiable in test suite)
    public void retainClaimAuthorizationCodeFlow(final String userId, final String userSecret, final String redirectUri) throws Exception {
        showTitle("authorizationCodeFlow");

        List<ResponseType> responseTypes = Arrays.asList(
                ResponseType.CODE,
                ResponseType.ID_TOKEN);
        List<String> scopes = Arrays.asList("openid", "profile", "address", "email", "phone", "user_name");

        String clientId = "0008-525a95a3-5fe1-4ecf-878c-06f438e3f500";
        String clientSecret = "V9RKUZOtfk92";//registerResponse.getClientSecret();

        // 2. Request authorization and receive the authorization code.
        String nonce = UUID.randomUUID().toString();
        AuthorizationResponse authorizationResponse = requestAuthorization(userId, userSecret, redirectUri, responseTypes, scopes, clientId, nonce);

        String scope = authorizationResponse.getScope();
        String authorizationCode = authorizationResponse.getCode();
        String idToken = authorizationResponse.getIdToken();

        // 3. Request access token using the authorization code.
        TokenRequest tokenRequest = new TokenRequest(GrantType.AUTHORIZATION_CODE);
        tokenRequest.setCode(authorizationCode);
        tokenRequest.setRedirectUri(redirectUri);
        tokenRequest.setAuthUsername(clientId);
        tokenRequest.setAuthPassword(clientSecret);
        tokenRequest.setAuthenticationMethod(AuthenticationMethod.CLIENT_SECRET_BASIC);

        TokenClient tokenClient1 = newTokenClient(tokenRequest);
        tokenClient1.setRequest(tokenRequest);
        TokenResponse tokenResponse1 = tokenClient1.exec();

        showClient(tokenClient1);
        assertEquals(tokenResponse1.getStatus(), 200, "Unexpected response code: " + tokenResponse1.getStatus());
        assertNotNull(tokenResponse1.getEntity(), "The entity is null");
        assertNotNull(tokenResponse1.getAccessToken(), "The access token is null");
        assertNotNull(tokenResponse1.getExpiresIn(), "The expires in value is null");
        assertNotNull(tokenResponse1.getTokenType(), "The token type is null");
        assertNotNull(tokenResponse1.getRefreshToken(), "The refresh token is null");

        String refreshToken = tokenResponse1.getRefreshToken();

        // 4. Validate id_token
        Jwt jwt = Jwt.parse(idToken);
        Asserter.assertIdToken(jwt, JwtClaimName.CODE_HASH);

        // 5. Request new access token using the refresh token.
        TokenClient tokenClient2 = new TokenClient(tokenEndpoint);
        tokenClient2.setExecutor(clientExecutor(true));
        TokenResponse tokenResponse2 = tokenClient2.execRefreshToken(scope, refreshToken, clientId, clientSecret);

        showClient(tokenClient2);
        assertEquals(tokenResponse2.getStatus(), 200, "Unexpected response code: " + tokenResponse2.getStatus());
        assertNotNull(tokenResponse2.getEntity(), "The entity is null");
        assertNotNull(tokenResponse2.getAccessToken(), "The access token is null");
        assertNotNull(tokenResponse2.getTokenType(), "The token type is null");
        assertNotNull(tokenResponse2.getRefreshToken(), "The refresh token is null");
        assertNotNull(tokenResponse2.getScope(), "The scope is null");

        String accessToken = tokenResponse2.getAccessToken();
        System.out.println("AT2: " + accessToken);

        Jwt at2Jwt = Jwt.parse(accessToken);
        System.out.println("AT2 claims: " + at2Jwt.getClaims().toJsonString());
    }

Client test output

#######################################################
TEST: authorizationCodeFlow
#######################################################
authenticateResourceOwnerAndGrantAccess: Cleaning cookies
authenticateResourceOwnerAndGrantAccess: authorizationRequestUrl:https://dluu.org/oxauth/restv1/authorize?response_type=code+id_token&client_id=0008-525a95a3-5fe1-4ecf-878c-06f438e3f500&scope=openid+profile+address+email+phone+user_name&redirect_uri=https%3A%2F%2Fdluu.org%2Foxauth-rp%2Fhome.htm&state=b3b84a89-93bf-43b1-b97d-97f967d9171d&nonce=4c7e063f-a7b3-4865-8183-da1d7399a42d
17:15:23.185 [main] ERROR com.gargoylesoftware.htmlunit.javascript.StrictErrorReporter - runtimeError: message=[An invalid or illegal selector was specified (selector: '*,:x' error: Invalid selector: :x).] sourceName=[https://dluu.org/oxauth/js/jquery-3.4.1.min.js] line=[2] lineSource=[null] lineOffset=[0]
authenticateResourceOwnerAndGrantAccess: sessionState:f6eb5c6f93a4e6dfdf961251e207fcd941fa4695fea9cae18fce288231b7caf6.ec2fea2e-c35a-4812-8fbe-d6f454281158
authenticateResourceOwnerAndGrantAccess: sessionId:469f56b5-13fd-45f1-b03f-9132a733aac1
-------------------------------------------------------
REQUEST:
-------------------------------------------------------
https://dluu.org/oxauth/restv1/authorize?response_type=code+id_token&client_id=0008-525a95a3-5fe1-4ecf-878c-06f438e3f500&scope=openid+profile+address+email+phone+user_name&redirect_uri=https%3A%2F%2Fdluu.org%2Foxauth-rp%2Fhome.htm&state=b3b84a89-93bf-43b1-b97d-97f967d9171d&nonce=4c7e063f-a7b3-4865-8183-da1d7399a42d

-------------------------------------------------------
RESPONSE:
-------------------------------------------------------
HTTP/1.1 302 Found
Location: https://dluu.org/oxauth-rp/home.htm#code=b52fa9ea-cfe2-45b7-b88a-040e6b969111&scope=openid+user_name+email&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJCMUYzLUFFQUUtQjc5OCIsImNvZGUiOiI4ZjkxYzcwNy1lODIwLTQwOWEtOGEwZS1lNmMwZjVjYzE2OTYiLCJhbXIiOlsiLTEiXSwiaXNzIjoiaHR0cHM6Ly9jZS1kZXY1LmdsdXUub3JnIiwibm9uY2UiOiI0YzdlMDYzZi1hN2IzLTQ4NjUtODE4My1kYTFkNzM5OWE0MmQiLCJzaWQiOiI4NzAyODQxMC1hMTY1LTRjNjEtOTA4Yy04NmY3ZTNmZWNjM2IiLCJveE9wZW5JRENvbm5lY3RWZXJzaW9uIjoib3BlbmlkY29ubmVjdC0xLjAiLCJhdWQiOiIwMDA4LTUyNWE5NWEzLTVmZTEtNGVjZi04NzhjLTA2ZjQzOGUzZjUwMCIsImFjciI6ImF1dGhfbGRhcF9zZXJ2ZXIiLCJjX2hhc2giOiJNRHp0eFFoRk9obFFXelVDdl9XVzdnIiwic19oYXNoIjoid1VGS0NFWV8xNWo4RFZKUUFsbTVrQSIsImF1dGhfdGltZSI6MTYxNDI2NjQ1MywiZXhwIjoxNjE0MjcwMDU0LCJncmFudCI6ImF1dGhvcml6YXRpb25fY29kZSIsImlhdCI6MTYxNDI2NjQ1NH0.aaeq4ZrOpWcMmcv69CgYaWNpR-DcLtkKUespStWZY_Y&session_id=469f56b5-13fd-45f1-b03f-9132a733aac1&state=b3b84a89-93bf-43b1-b97d-97f967d9171d&session_state=f6eb5c6f93a4e6dfdf961251e207fcd941fa4695fea9cae18fce288231b7caf6.ec2fea2e-c35a-4812-8fbe-d6f454281158&sid=87028410-a165-4c61-908c-86f7e3fecc3b

-------------------------------------------------------
REQUEST:
-------------------------------------------------------
POST /oxauth/restv1/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: dluu
Authorization: Basic MDAwOC01MjVhOTVhMy01ZmUxLTRlY2YtODc4Yy0wNmY0MzhlM2Y1MDA6VjlSS1VaT3Rmazky

grant_type=authorization_code&code=b52fa9ea-cfe2-45b7-b88a-040e6b969111&redirect_uri=https%3A%2F%2Fdluu%2Foxauth-rp%2Fhome.htm

-------------------------------------------------------
RESPONSE:
-------------------------------------------------------
HTTP/1.1 200
Cache-Control: no-store
Connection: Keep-Alive
Content-Length: 1794
Content-Type: application/json
Date: Thu, 25 Feb 2021 15:20:54 GMT
Keep-Alive: timeout=5, max=100
Pragma: no-cache
Server: Apache/2.4.29 (Ubuntu)
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Xss-Protection: 1; mode=block

{"access_token":"eyJraWQiOiI0MDM5ZjZkYi1hZTM4LTQ5MzQtOGI1ZC1jYTkzNzdmODljNWRfc2lnX3JzMjU2IiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJhdWQiOiIwMDA4LTUyNWE5NWEzLTVmZTEtNGVjZi04NzhjLTA2ZjQzOGUzZjUwMCIsInN1YiI6IkIxRjMtQUVBRS1CNzk4IiwieDV0I1MyNTYiOiIiLCJjb2RlIjoiZDFkNmZjY2ItZWUyMS00NmI3LTg0Y2YtMTQ4M2ViZDFkODU3Iiwic2NvcGUiOlsib3BlbmlkIiwidXNlcl9uYW1lIiwiZW1haWwiXSwiaXNzIjoiaHR0cHM6Ly9jZS1kZXY1LmdsdXUub3JnIiwiY2xhaW0xIjoidmFsdWUxIiwidG9rZW5fdHlwZSI6ImJlYXJlciIsImV4cCI6MTYxNDI2Njc1NCwiaWF0IjoxNjE0MjY2NDU0LCJjbGllbnRfaWQiOiIwMDA4LTUyNWE5NWEzLTVmZTEtNGVjZi04NzhjLTA2ZjQzOGUzZjUwMCIsInVzZXJuYW1lIjoib3hBdXRoIFRlc3QgVXNlciJ9.Rm5kc6g1CXt9EL9J-we83iy9_MsNiJjVtWW_0IfkT5UGCGTx7Td6f2pyvurcFTNNyrANP6yI6oYIBK_yqldfQ-zG3YxVBMidPCQo8dcYU9nJlZaq11R6glYEDOrnMrLTu9YM2CciyhtMEfHyhL0p2XYgkpdjbaa3GWt-dmh5bqqAJ2pP-59e2MDCcskrBOFUaIaCy7g2odYn9HUPcKKJB-PVVHUn-oNssE6kVLkT0B9fb75tutf9tT5JHiX4VRdAarfIVPl1JEnjws16UY4mClaQ7NuDM92PqyxVuVaE1EZgXeX26VqAkiawNB0lmwd4oHhCDV7YHoyhFr3H1eCPAg","refresh_token":"5a80dffa-f96b-4b62-8f5b-c933b10cf513","id_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdF9oYXNoIjoicmJkdE9sdHEyOHIyNlZCZTFXMjhSQSIsInN1YiI6IkIxRjMtQUVBRS1CNzk4IiwiY29kZSI6IjI2OGVlZjY1LTUyNDMtNDQ1Mi05Zjg2LWMyMDhlMjVmNWY3ZCIsImFtciI6WyItMSJdLCJpc3MiOiJodHRwczovL2NlLWRldjUuZ2x1dS5vcmciLCJub25jZSI6IjRjN2UwNjNmLWE3YjMtNDg2NS04MTgzLWRhMWQ3Mzk5YTQyZCIsInNpZCI6Ijg3MDI4NDEwLWExNjUtNGM2MS05MDhjLTg2ZjdlM2ZlY2MzYiIsIm94T3BlbklEQ29ubmVjdFZlcnNpb24iOiJvcGVuaWRjb25uZWN0LTEuMCIsImF1ZCI6IjAwMDgtNTI1YTk1YTMtNWZlMS00ZWNmLTg3OGMtMDZmNDM4ZTNmNTAwIiwiYWNyIjoiYXV0aF9sZGFwX3NlcnZlciIsImNfaGFzaCI6Ik1EenR4UWhGT2hsUVd6VUN2X1dXN2ciLCJhdXRoX3RpbWUiOjE2MTQyNjY0NTMsImV4cCI6MTYxNDI3MDA1NSwiZ3JhbnQiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJpYXQiOjE2MTQyNjY0NTV9.AACxuBXsPgssVVlbzVD-XJCq3KZTdC4wRpUotV5Qsr4","token_type":"bearer","expires_in":299}

-------------------------------------------------------
REQUEST:
-------------------------------------------------------
POST /oxauth/restv1/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: dluu
Authorization: Basic MDAwOC01MjVhOTVhMy01ZmUxLTRlY2YtODc4Yy0wNmY0MzhlM2Y1MDA6VjlSS1VaT3Rmazky

grant_type=refresh_token&scope=openid+user_name+email&refreshToken=5a80dffa-f96b-4b62-8f5b-c933b10cf513

-------------------------------------------------------
RESPONSE:
-------------------------------------------------------
HTTP/1.1 200
Cache-Control: no-store
Connection: Keep-Alive
Content-Length: 1228
Content-Type: application/json
Date: Thu, 25 Feb 2021 15:20:55 GMT
Keep-Alive: timeout=5, max=100
Pragma: no-cache
Server: Apache/2.4.29 (Ubuntu)
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Xss-Protection: 1; mode=block

{"access_token":"eyJraWQiOiI0MDM5ZjZkYi1hZTM4LTQ5MzQtOGI1ZC1jYTkzNzdmODljNWRfc2lnX3JzMjU2IiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJCMUYzLUFFQUUtQjc5OCIsImNvZGUiOiJkODc2OTg3My1hMTIzLTQxOWUtYWE5ZS1kNDU4NzgyYTE2MzkiLCJpc3MiOiJodHRwczovL2NlLWRldjUuZ2x1dS5vcmciLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwiY2xpZW50X2lkIjoiMDAwOC01MjVhOTVhMy01ZmUxLTRlY2YtODc4Yy0wNmY0MzhlM2Y1MDAiLCJhdWQiOiIwMDA4LTUyNWE5NWEzLTVmZTEtNGVjZi04NzhjLTA2ZjQzOGUzZjUwMCIsIng1dCNTMjU2IjoiIiwicmVmcmVzaF90b2tlbiI6IjVhODBkZmZhLWY5NmItNGI2Mi04ZjViLWM5MzNiMTBjZjUxMyIsInNjb3BlIjpbIm9wZW5pZCIsInVzZXJfbmFtZSIsImVtYWlsIl0sImdyYW50X2lkIjoiOTc2NTBmYWMtYWE0NS00MDY4LWEzNWItYmY1YWVlMjYxZDU3IiwiY2xhaW0xIjoidmFsdWUxIiwiZXhwIjoxNjE0MjY2NzU1LCJpYXQiOjE2MTQyNjY0NTUsInVzZXJuYW1lIjoib3hBdXRoIFRlc3QgVXNlciJ9.uIuzMiPm_PL2rtHH3mwq74EG975928DCUUL2w6WtT_zbgweMYdZSCdxEBuRC5S0tFIX4w9T93AtRasjzZd0a0qxHQY9Qm3_zey6rUoa4NntPNapAwY9qHETvNEkIj8fplt-nttojogZ5l2nqiPXCY1CKt9zLx7_QoCaBfWGZuigD0agI80wQzZ9yJOtVCGgRwr7WdFwB1PasXn_yjhYqpkN3WffY-hv3QtLYKWzXUFGxVQKbjeANei0CjdTBD5MNHcvydJo6ca3g5fo7TwsOlUYlldOLAJzoR3Yh8CupAtLc0SuXvJqXqjdxHVwLI-AqNyjq2eMxLU97sI68Nb46yA","refresh_token":"7ac697ec-6764-4624-b34f-9b33abe3e865","scope":"openid user_name email","token_type":"bearer","expires_in":299}

AT2: eyJraWQiOiI0MDM5ZjZkYi1hZTM4LTQ5MzQtOGI1ZC1jYTkzNzdmODljNWRfc2lnX3JzMjU2IiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJCMUYzLUFFQUUtQjc5OCIsImNvZGUiOiJkODc2OTg3My1hMTIzLTQxOWUtYWE5ZS1kNDU4NzgyYTE2MzkiLCJpc3MiOiJodHRwczovL2NlLWRldjUuZ2x1dS5vcmciLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwiY2xpZW50X2lkIjoiMDAwOC01MjVhOTVhMy01ZmUxLTRlY2YtODc4Yy0wNmY0MzhlM2Y1MDAiLCJhdWQiOiIwMDA4LTUyNWE5NWEzLTVmZTEtNGVjZi04NzhjLTA2ZjQzOGUzZjUwMCIsIng1dCNTMjU2IjoiIiwicmVmcmVzaF90b2tlbiI6IjVhODBkZmZhLWY5NmItNGI2Mi04ZjViLWM5MzNiMTBjZjUxMyIsInNjb3BlIjpbIm9wZW5pZCIsInVzZXJfbmFtZSIsImVtYWlsIl0sImdyYW50X2lkIjoiOTc2NTBmYWMtYWE0NS00MDY4LWEzNWItYmY1YWVlMjYxZDU3IiwiY2xhaW0xIjoidmFsdWUxIiwiZXhwIjoxNjE0MjY2NzU1LCJpYXQiOjE2MTQyNjY0NTUsInVzZXJuYW1lIjoib3hBdXRoIFRlc3QgVXNlciJ9.uIuzMiPm_PL2rtHH3mwq74EG975928DCUUL2w6WtT_zbgweMYdZSCdxEBuRC5S0tFIX4w9T93AtRasjzZd0a0qxHQY9Qm3_zey6rUoa4NntPNapAwY9qHETvNEkIj8fplt-nttojogZ5l2nqiPXCY1CKt9zLx7_QoCaBfWGZuigD0agI80wQzZ9yJOtVCGgRwr7WdFwB1PasXn_yjhYqpkN3WffY-hv3QtLYKWzXUFGxVQKbjeANei0CjdTBD5MNHcvydJo6ca3g5fo7TwsOlUYlldOLAJzoR3Yh8CupAtLc0SuXvJqXqjdxHVwLI-AqNyjq2eMxLU97sI68Nb46yA
AT2 claims: {"sub":"B1F3-AEAE-B798","code":"d8769873-a123-419e-aa9e-d458782a1639","iss":"https://dluu.org","token_type":"bearer","client_id":"0008-525a95a3-5fe1-4ecf-878c-06f438e3f500","aud":"0008-525a95a3-5fe1-4ecf-878c-06f438e3f500","x5t#S256":"","refresh_token":"5a80dffa-f96b-4b62-8f5b-c933b10cf513","scope":["openid","user_name","email"],"grant_id":"97650fac-aa45-4068-a35b-bf5aee261d57","claim1":"value1","exp":1614266755,"iat":1614266455,"username":"oxAuth Test User"}