Error : The client_assertion token has already been used

I am not sure if Apigee is the correct forum to ask this.
We are passing client_assertion token okta endpoint 'oauth2/v1/token'

There is a logic to generate the client_assertion token and during each call a different client_assertion token is generated and passed to 'oauth2/v1/token'

When we hit apigee proxy endpoint the 200 response is returned. When we immediately hit same endpoint, we get ServiceCallout SC-GetOKTAToken failed. Reason: ResponseCode 401 is treated as error.

When I took the client_assertion token from the error response trace and directly hit the 'oauth2/v1/token' endpoint I get
Error : The client_assertion token has already been used.

The 200 response and 401 response client_assertion tokens are totally different.

When I again hit apigee endpoint may be after 2 mins I get 200 response. Again if I hit after 25 seconds I get 401 response. This loops keep repeating after every 2 mins

We are using python to generate client_assertion token and using BioConnect Flow

Attached is the proxy bundle.

Not sure if the concept of JWT is going wrong or its some logic within Apigee which is passing old JWT token

Solved Solved
0 9 253
1 ACCEPTED SOLUTION

I think I have found out the issue.

The "jti":"0fafbbd8-cc2a-49bf-b93c-103ac8b7dc87" is causing this problem. We are passing same value in each call.

After deleting this field , we are not getting the error.

View solution in original post

9 REPLIES 9

I cannot find and option to upload proxy bundle here.

For me, when I am posting a message or a reply, there is a widget at the bottom of the text box where I type my message. The widget says "Drag and drop or browse files to attach". 

screenshot-20220729-113458.png

Let me understand better. Which actor is generating the client assertion?  I think you said you are generating it from within that python script.  Does the python script run within the scope of an Apigee API Proxy ?  Or is it running outside of Apigee?

Also I want to confirm that the error message you see, "The client_assertion token has already been used." is coming from Okta.  Is that right?  Apigee is receiving it from Okta, Right?

I don't know how Okta checks that the token has never been used.  Have you verified that the JTI is distinct from each successive assertion?  Can you show two of the different assertions here? (Paste in the JWT)

I cannot imagine how Apigee would be replaying a JWT (client_assertion) from a prior request.  That would be a neat trick. 

It's also possible that the error message from Okta is misleading. Is it possible that Okta is rejecting the client_assertion because at most only 1 token can be obtained for a givren time window, for a given client?  If this were the case then you should be able to reproduce the problem even if you eliminate the Apigee part from the mix.  Generate the assertion in python, then send it to Okta, and then do the same thing again, and see the same rejection.  I mean to say if you DO see the same error, then that would support  this theory.  

You may get better results asking Okta about how/when this error gets sent back. They have a developer forum too. 

 

praviningawale_0-1659121025172.png

Strange I do not have option to upload it

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Javascript continueOnError="false" enabled="true" timeLimit="200" name="JS-SetHeaderPayload">
    <DisplayName>JS-SetHeaderPayload</DisplayName>
    <Properties/>
    <ResourceURL>jsc://JS-SetHeaderPayload.js</ResourceURL>
</Javascript>


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Script continueOnError="false" enabled="true" name="PY-GetAWSSignature">
    <DisplayName>PY-GetAWSSignature</DisplayName>
    <Properties/>
    <ResourceURL>py://PY-GetAWSSignature.py</ResourceURL>
</Script>


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage name="AM-Construct-Outgoing-AWS-Message">
    <AssignTo createNew="true" type="request">outgoingAwsMessage</AssignTo>
    <Set>
        <Headers>
            <!-- per https://docs.aws.amazon.com/kms/latest/APIReference/kms-api-reference.pdf -->
            <Header name="X-Amz-Target">TrentService.Sign</Header>
        </Headers>
        <QueryParams>
            <QueryParam name="Version">2014-11-01</QueryParam>
            <QueryParam name="Action">Sign</QueryParam>
        </QueryParams>
        <Verb>POST</Verb>
        <Payload contentType="application/x-amz-json-1.1">
            {
                  "KeyId": "23c29362-cd00-4c9c-803f-c1ee961be6c3",
                  "Message": "{aws_msg}" ,
                  "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
            }
        </Payload>
    </Set>
</AssignMessage>



<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout name="JC-AWSSignV4">
    <Properties>
        <Property name="service">kms</Property>
        <Property name="endpoint">https://kms.us-east-1.amazonaws.com</Property>
        <Property name="region">us-east-1</Property>
        <Property name="key">XXXX</Property>
        <Property name="secret">CCCCCC</Property>
        <Property name="source">outgoingAwsMessage</Property>
        <Property name="sign-content-sha256">true</Property>
        <!--<Property name="debug">true</Property>-->
    </Properties>
    <ClassName>com.google.apigee.callouts.AWSV4Signature</ClassName>
    <ResourceURL>java://apigee-callout-awsv4sig-20210609.jar</ResourceURL>
</JavaCallout>



<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout name="SC-Send-AWS-Message">
    <Request variable="outgoingAwsMessage"/>
    <Response>awsresponse</Response>
    <HTTPTargetConnection>
        <!--<URL>https://kms.us-east-1.amazonaws.com/?Action=Sign</URL>-->
        <URL>https://kms.us-east-1.amazonaws.com</URL>
    </HTTPTargetConnection>
</ServiceCallout>



<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage name="AM-Diagnostics">
    <AssignVariable>
        <Name>diagnostics</Name>
        <Ref>awsresponse.content</Ref>
    </AssignVariable>
</AssignMessage>


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ExtractVariables continueOnError="false" enabled="true" name="EV-Sign">
    <DisplayName>EV-Sign</DisplayName>
    <Properties/>
    <JSONPayload>
        <Variable name="aws_signature">
            <JSONPath>$.Signature</JSONPath>
        </Variable>
    </JSONPayload>
    <Source clearPayload="false">awsresponse.content</Source>
</ExtractVariables>


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Script continueOnError="false" enabled="true" name="PY-SetNull">
    <DisplayName>PY-SetNull</DisplayName>
    <Properties/>
    <ResourceURL>py://PY-SetNull.py</ResourceURL>
</Script>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Script continueOnError="false" enabled="true" name="PY-GetJWTToken">
    <DisplayName>PY-GetJWTToken</DisplayName>
    <Properties/>
    <ResourceURL>py://PY-GetJWTToken.py</ResourceURL>
</Script>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout continueOnError="false" enabled="true" name="SC-GetOKTAToken">
    <DisplayName>SC-GetOKTAToken</DisplayName>
    <Properties/>
    <Request>
        <Set>
            <Path>{servicecallout.request.path}</Path>
            <Headers>
                <Header name="Content-Type">application/x-www-form-urlencoded</Header>
            </Headers>
            <FormParams>
                <FormParam name="grant_type">client_credentials</FormParam>
                <FormParam name="scope">okta.users.manage</FormParam>
                <FormParam name="client_assertion_type">urn:ietf:params:oauth:client-assertion-type:jwt-bearer</FormParam>
                <FormParam name="client_assertion">{finalJWT}</FormParam>
            </FormParams>
        </Set>
    </Request>
    <Response>tokenresponse</Response>
    <HTTPTargetConnection>
        <Properties/>
        <URL>http://example.com</URL>
    </HTTPTargetConnection>
</ServiceCallout>


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ExtractVariables continueOnError="false" enabled="true" name="EV-OKTAToken">
    <DisplayName>EV-OKTAToken</DisplayName>
    <Properties/>
    <JSONPayload>
        <Variable name="OKTAAccess_token">
            <JSONPath>$.access_token</JSONPath>
        </Variable>
    </JSONPayload>
    <Source clearPayload="false">tokenresponse</Source>
</ExtractVariables>

 

--JS-SetHeaderPayload.js

var header = {"alg": "RS256", "typ": "JWT"};
var sys_timestamp = context.getVariable('system.timestamp');	

var iat2 = Math.floor(sys_timestamp / 1000);
var exp2 = iat2 + 10;

var payload = {"iat": iat2, "exp": exp2, "sub": "0oa199ehkxsd0KZDi0h8", "iss": "0oa199ehkxsd0KZDi0h8", "aud": "https://mmc.oktapreview.com/oauth2/v1/token", "jti": "0fafbbd8-cc2a-49bf-b93c-103ac8b7dc87"};


context.setVariable('js_header', JSON.stringify(header));
context.setVariable('js_payload', JSON.stringify(payload));
context.setVariable('iat', iat2); 
context.setVariable('exp', exp2); 





--PY-GetAWSSignature.py
import base64


new_header = flow.getVariable("js_header")
new_payload = flow.getVariable("js_payload")
new_iat = flow.getVariable("iat")
new_exp = flow.getVariable("exp")


payload = {"iat": new_iat, "exp": new_exp, "sub": "0oa199ehkxsd0KZDi0h8", "iss": "0oa199ehkxsd0KZDi0h8", "aud": "https://mmc.oktapreview.com/oauth2/v1/token", "jti": "0fafbbd8-cc2a-49bf-b93c-103ac8b7dc87"}

token_components2 = {
        "header":  base64.urlsafe_b64encode(new_header.encode()).decode().rstrip("="),
        "payload": base64.urlsafe_b64encode(new_payload.encode()).decode().rstrip("="),
    }
    
message2 = token_components2["header"]+ "."+ token_components2["payload"]

aws_message = base64.b64encode(message2)

flow.setVariable("aws_msg",aws_message)

flow.setVariable("T2",message2) 


----PY-GetJWTToken.py

import base64

header_payroll = flow.getVariable("T2")
aws_sign = flow.getVariable("aws_signature")
sig_base64 = aws_sign
signature = base64.b64decode(sig_base64)
signature_urlsafe = base64.urlsafe_b64encode(signature).decode().rstrip("=")


finalJWT_old = header_payroll + "."+ signature_urlsafe
flow.setVariable("finalJWT",finalJWT_old)

I think I have found out the issue.

The "jti":"0fafbbd8-cc2a-49bf-b93c-103ac8b7dc87" is causing this problem. We are passing same value in each call.

After deleting this field , we are not getting the error.

That makes sense to me. The jti is defined to be a unique identifier, unique to a particular jwt. so probably okta was using that as a distinguisher.

i’m glad you found it. Why are you using python to generate the JWT? If you had used the built-in generatejwt policy, you would not have experienced this problem, because it always applies a unique ID (jti).  

We were using JWT policy earlier. But our group infosec team has suggested to use AWS KMS . The reason for this is that they do not want to store private key in apigee KVM.  I agree that the solution which we are using now is not appropriate and we are taking a long road to achieve the desired result.