Client Credentials OAuth for Okta with DPoP enabled

Followed steps in the docs but it’s a mix of Implement OAuth for Okta with a service app | Okta Developer and Configure OAuth 2.0 Demonstrating Proof-of-Possession | Okta Developer. I get the access_token but calling Okta API /api/v1/users returns HTTP 400.

  • Create App Integration > select API Services

  • Edit Client Credentials > select Public key / Private key > Add key > copy private JWK key > Save

  • In General Settings by default Require Demonstrating Proof of Possession(DPoP) header in token requests is True

  • Okta API Scopes Tab > Grant okta.users.read

  • Admin Roles Tab > Assign Read-only Administrator > Save changes

  • Convert private JWK key to public PEM and private PEM (I use 8gwifi .org/jwkconvertfunctions.jsp)

  • Create and sign the client_credentials JWT using jwt .io

Payload:

{
"aud": "https://dev-<account#> .okta .com/oauth2/v1/token",
"iss": "<app_client_id>",
"sub": "<app_client_id>",
"exp": <epoch in future>
}
  • Create and sign the DPoP JWT using jwt .io

Header:

{
"typ": "dpop+jwt",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "<private key kid>",
"alg": "RS256",
"n": "<private key n>"
}
}

Payload:


{
"htm": "POST",
"htu": "https://dev-<account#> .okta .com/oauth2/v1/token",
"iat": <epoch now>
}
  • Call POST on dev-<account#>.okta.com/oauth2/v1/token
curl --location 'dev-<account#>.okta .com/oauth2/v1/token' \
--header 'DPoP: <DPoP JWT>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=okta.users.read' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'client_assertion=<client_credentials JWT>'
  • Receive HTTP 400 and use response header ‘dpop-nonce’ to create new DPoP JWT
{
"error": "use_dpop_nonce",
"error_description": "Authorization server requires nonce in DPoP proof."
}
  • Create and sign the DPoP with nonce JWT using jwt .io by using the same header but adding nonce and jti in payload
{
"htm": "POST",
"htu": "dev-<account#>.okta .com/oauth2/v1/token",
"iat": <epoch now>,
"nonce": "<response header dpop-nonce from HTTP 400>",
"jti": "<any random character>"
}
  • Call the same POST on dev-<account#>.okta .com/oauth2/v1/token but change DPoP header with the new DPoP with nonce JWT

Receive a HTTP 200

{
"token_type": "DPoP",
"expires_in": 3600,
"access_token": "<access_token with DPoP>",
"scope": "okta.users.read"
}
  • Call GET on dev-<account#>.okta .com/oauth2/v1/users

curl --location 'dev-<account#>.okta .com/api/v1/users' \
--header 'DPoP: <DPoP JWT>' \
--header 'Authorization: DPoP <access_token with DPoP>'
  • HTTP 400 Bad Request with a blank body

How do I fix this and which step is it failing?

Thank you for writing to Okta Dev Forum.

My name is Akash, from Okta and I will be assisting you with this issue.

Looking into the logs, I have observed that the provided DPoP Proof JWT is expired when the request was made to the token endpoint.

Could you please decode the token using this tool - jwt.io and ensure that the token is not expired.

If you are using DPoP, you will have to generate DPoP JWT for every call you make and can be used only once. For example, when you call

curl --location 'dev-21508607.okta .com/api/v1/users' \

--header 'DPoP: <DPoP proof JWT>' \

--header 'Authorization: DPoP <access_token with DPoP>'

DPoP proof JWT referred here would need the correct payload like this,

{
    "htm": "GET",
    "htu": "https://dev-21508607.okta .com/api/v1/users",
    "iat": <time in epoch>,
    "ath": "<Base64-encoded SHA-256 hash [SHS] of the DPoP-bound access token>",
    "jti": "<unique identifier>"
}

This will enable your call to succeed once and would require you to mint new DPoP proof JWT every single time. Here is the reference you can look at.

3 Likes

Hey akash-okta, thanks for checking the logs. I have made numerous calls to the token endpoint and one of them could have been expired. As I have mentioned in my post, I am able to receive a HTTP 200 from the token endpoint

{
"token_type": "DPoP",
"expires_in": 3600,
"access_token": "<access_token with DPoP>",
"scope": "okta.users.read"
}

I just made another token endpoint. Can you check the logs?

A more useful log would be to my /api/v1/users endpoint

curl --location 'dev-<account#>.okta .com/oauth2/v1/users' \
--header 'DPoP: <DPoP JWT>' \
--header 'Authorization: DPoP <access_token with DPoP>'

I get HTTP 400 but no response or any error code. Can you guide me where I can find my logs for this?

Hey ram.gandhi, I completely missed that. Thanks for pointing that out. I tried creating the correct DPoP proof JWT but I still get a HTTP 400 on my /api/v1/users endpoint.

These are the steps that I did.

  • After receiving HTTP 200 on oauth2/v1/token and getting access_token with DPoP
  • Get the Base64-encoded SHA-256 hash [SHS] of the access_token with DPoP. I used SHA256 - Online Tools with Input Encoding UTF-8 and Output Encoding Base64
  • Create and sign the DPoP proof JWT with ath and different jti using jwt .io

Header:

{
"typ": "dpop+jwt",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "<private key kid>",
"alg": "RS256",
"n": "<private key n>"
}
}

Payload:

{
"htm": "GET",
"htu": "https://dev-<account#> .okta .com/api/v1/users",
"iat": <epoch now>
"ath": "<Base64-encoded SHA-256 hash [SHS] of the access_token with DPoP>",
"jti": "<new random string"
}
  • Call GET call on /api/v1/users
curl --location 'dev-<account#>.okta .com/api/v1/users' \
--header 'DPoP: <DPoP proof JWT with `ath` and different `jti` >' \
--header 'Authorization: DPoP <access_token with DPoP>'

I still get a HTTP 400 on /api/v1/users. Is there anyway I can find the logs for it?

As far as logs, these won’t be available in your tenant and can only be retrieved through internal logs.

However you can try this in the mean time, I tried using the link you shared to generate ath value. I had to base64URLencode on top of it. Here are the steps I followed

  • Get the Base64-encoded SHA-256 hash [SHS] of the access_token with DPoP. I used SHA256 - Online Tools with Input Encoding UTF-8 and Output Encoding Base64
  • Run echo '<output for previous step>' | tr '/+' '_-' | tr -d '=' in bash to get ath value
  • Create and sign the DPoP proof JWT with ath and different jti using jwt .io

You can look at our SDKs generating ath values like the below

3 Likes

@ram.gandhi that worked! Thanks a lot for your help! I am now getting HTTP 200 on my /api/v1/users/ endpoint.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.