Getting a 403 when attempting to exchange an authorization code for an access token

We have a backend service that uses the Okta API for authentication and now we are trying to also implement the OAuth2 3 legged flow using the API.

I was able to get an authentication code using this endpoint:
/oauth2/default/v1/authorize?client_id=<client_id>&response_type=code&response_mode=query&scope=openid&redirect_uri=
&state=&sessionToken=<the session token I got from the /authn endpoint>&nonce=1234567890

But when I try to exchange it for an access token I get a 403. I tried using this endpoint {{url}}/oauth2/{{authorizationServerId}}/v1/token both in POSTMAN and CURL.

From CURL I was able to get more detail:
{“error”:“invalid_client”,“error_description”:“Invalid value for ‘client_id’ parameter.”}

So I triple checked that I am using the correct client id and I am and I am using the same client id for both the “authorize” call and the “token” call. One is successful, the other fails with the above mentioned error.

I am passing the client_id in the body of the request, not in the Basic Auth headers.

Any thoughts?

Can you check to make sure Base Url and the {{authorizationServerId}}is the same for both the authorize and token call?

Can you also confirm that you aren’t accidentally passing the Basic authorization header AND the client_id in the body? I believe Basic authorization header trumps the body, so the client_id might be incorrect there.

Base url and authorization server id are the same, I hardcoded them when doing the CURL requests. I am not passing the Headers (I explicitly removed them in POSTMAN and never used them in CURL). This is the CURL request I am making:

curl -X POST
“<base_url>/oauth2/default/v1/token”
-H “Accept: application/json”
-H “Cache-Control: no-cache”
-H “Content-Type: application/x-www-form-urlencoded”
-d “grant_type=authorization_code&code=&
scope=openid&client_id=<client_id>&client_secret=<client_secret>&redirect_uri=<redirect_uri>”

I noticed the client ID and secret are Base64 encoded in the headers. I am not encoding them in the body. Also, I am not using and single or double quotes. Do I need to do any of these?

Can you share your /.well-known/openid-configuration endpoint?

For example:
https://tom-okta.oktapreview.com/oauth2/default/.well-known/openid-configuration

I want to make sure I’m not missing anything. For example, that your authorization server isn’t configured correctly to support client_secret_post

Sure, it looks like that config option is there:
https://bazaarvoice-portal-dev-us.oktapreview.com/oauth2/default/.well-known/oauth-authorization-server

Hmmm…

Do you mind sending us an email at developers@okta.com? I think we are going to need to do some additional troubleshooting that the forum isn’t appropriate for. Feel free to reference this post.

Does the /tokens endpoint require any cookies to be passed in? I am calling it from a backend app.

No cookies are required

ok, I figured it out. I was not passing in the state query param in my /token request. As soon as I did that, I was able to get a token.
By the way, there is NO mention of this in the documentation. It says it’s only mandatory in the /authorize endpoint. It’s not even listed as a potential query param in the /token endpoint.

I validated this hypothesis by repeatedly adding and removing that param from the /token request with the same results:

  • state missing: error: “error”:“invalid_client”,“error_description”:“Invalid value for ‘client_id’ parameter.”
  • state present: got back an access token

Ack… sorry you ran into that. I know how frustrating incorrect error messages can be. I’m going to log something for the team to fix. Let me know any other questions!

It’s more than just incorrect error messaging, it’s also missing documentation. Look here: https://developer.okta.com/docs/api/resources/oauth2#request-a-token

There is no mention of the “state” parameter in the /token endpoint, less alone that it is mandatory. I’m not sure if I’m doing something crazy or if it’s an API change that went undocumented.

AFAIK, if you pass the client id + client secret to the /token endpoint, you don’t have to pass the state parameter. I remember testing this endpoint by using both these parameters (state or client_id + client_secret) to get the access token from an Okta Auth server. The OAuth 2 spec also doesn’t mandate passing the state parameter if you pass client_id & client_secret.

Here’s some reference on token authentication - https://developer.okta.com/docs/api/resources/oauth2#token-authentication-methods

Having said that, I’m not sure why you got this error. I will try to test it in my environment and see if I can reproduce it.

Yea, I just realized that the endpoint does not require state, so I’m unsure what is going on.

I am passing both client_id and client_secret in the body of the request, not the headers. Could that be the problem?

No. That shouldn’t be a problem.
You can pass base64 encoded clientId + client secret as Basic Auth header OR as url parameters to the POST request. You’re doing the latter and it should have worked.

Can you try the following 2 methods -

  1. Remove the scope url parameter and execute POST on /token endpoint

curl -X POST
“<base_url>/oauth2/default/v1/token”
-H “Accept: application/json”
-H “Cache-Control: no-cache”
-H “Content-Type: application/x-www-form-urlencoded”
-d “grant_type=authorization_code&code=<your_code>&client_id=<client_id>&client_secret=<client_secret>&redirect_uri=<redirect_uri>”

  1. Base64 url encode this string - client_id:client_secret (including the :) and pass it as a Basic Auth header -
    curl -X POST
    “<base_url>/oauth2/default/v1/token”
    -H “Accept: application/json”
    -H “Cache-Control: no-cache”
    -H “Content-Type: application/x-www-form-urlencoded”
    -H “Authorization: Basic <Base64_url_encoded_string>”
    -d “grant_type=authorization_code&code=<your_code>&redirect_uri=<redirect_uri>”

Let us know if either of these calls returns the access token.

  1. Removing the scope did not do anything
  2. Using Basic Auth instead of url params worked in CURL. However, Postman still returns 403, but who cares?

So indeed it looks like there might be a problem with the client_id and client_secret being passed in as url params

We are still unable to reproduce, I think there is something else going on here that might be related to your org configuration.

I need you to work with our support team at developers@okta.com to get more information to see if we can get a reproducible scenario. Definitely point to this post, because I believe we have done a sufficient set of troubleshooting, so they shouldn’t need to start from scratch.

Thanks,
Tom

Hi @tom, I am getting the same response while exchanging authorization code with the access token via the token end point. The above solution did not worked for me.
I am developing a iOS SDK for Authentication and authorization for my organisation.
For API calls i am using Alamofire.
Deployment target is iOS 11.4
Below are the details for each of my webservice calls :

  1. Authn endpoint
    REQUEST -
    *$ curl -v *

*-X POST *

*-b “proximity_beb48734015c875160c574c2696a4f68=Ej60dXl725NxOJ0C6TgeKyDHKkJMPV4YpgMHAuXzLsKdpa25jeuSBgIgFOwXy5fnoDY3f7BAlTfOtVjAjTuIdf3xsZXAy6NHP9TKbhxZvLa62cYvmQ/X2ARMwV8sJtzwK0CeEwdqanhIwblGYOpcCtRHoqkJqqfWgZnDhqZTwcplCoNNK3FFtVaH53jsqZXY;DT=DI0YLv4MQBGQlusEP4JiLfBCg” *

*-H “Content-Type: application/json” *

*-H “Accept-Language: en;q=1.0” *

*-H “User-Agent: AuthenticationSample/1.0 (PB.AuthenticationSample; build:1; iOS 11.4.0) Alamofire/4.8.0” *

*-H “Accept-Encoding: gzip;q=1.0, compress;q=0.5” *

*-d “{“username”:“XXX@XXX.com”,“password”:“somepassword”}” *

"https://{domain}/api/v1/authn"

RESPONSE-
<NSHTTPURLResponse: 0x610000026280> { URL: https://{domain}/api/v1/authn } { Status Code: 200, Headers {
“Cache-Control” = (
“no-cache, no-store”
);
Connection = (
“Keep-Alive”
);
“Content-Encoding” = (
gzip
);
“Content-Type” = (
“application/json;charset=UTF-8”
);
Date = (
“Thu, 03 Jan 2019 07:30:41 GMT”
);
Expires = (
0
);
“Keep-Alive” = (
“timeout=315, max=200”
);
P3P = (
“CP=“HONK””
);
Pragma = (
“no-cache”
);
“Public-Key-Pins-Report-Only” = (
“pin-sha256=“jZomPEBSDXoipA9un78hKRIeN/+U4ZteRaiX8YpWfqc=”; pin-sha256=“axSbM6RQ+19oXxudaOTdwXJbSr6f7AahxbDHFy3p8s8=”; pin-sha256=“SE4qe2vdD9tAegPwO79rMnZyhHvqj3i5g1c2HkyGUNE=”; pin-sha256=“ylP0lMLMvBaiHn0ihLxHjzvlPVQNoyQ+rMiaj0da/Pw=”; max-age=60; report-uri=“https://okta.report-uri.io/r/default/hpkp/reportOnly””
);
Server = (
nginx
);
“Set-Cookie” = (
“sid=”"; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/",
“JSESSIONID=F731CD24B428B6D2ACD6E807E0F50900; Path=/; Secure; HttpOnly”
);
“Strict-Transport-Security” = (
“max-age=315360000”
);
“Transfer-Encoding” = (
Identity
);
Vary = (
“Accept-Encoding”
);
“X-Content-Type-Options” = (
nosniff
);
“X-Okta-Request-Id” = (
XC26IGe1wfLFB5epb2v0BgAAAbQ
);
“X-Rate-Limit-Limit” = (
600
);
“X-Rate-Limit-Remaining” = (
592
);
“X-Rate-Limit-Reset” = (
1546500685
);
} }

  1. Authorize
    REQUEST-
    *$ curl -v *

*-b “JSESSIONID=F731CD24B428B6D2ACD6E807E0F50900;proximity_beb48734015c875160c574c2696a4f68=Ej60dXl725NxOJ0C6TgeKyDHKkJMPV4YpgMHAuXzLsKdpa25jeuSBgIgFOwXy5fnoDY3f7BAlTfOtVjAjTuIdf3xsZXAy6NHP9TKbhxZvLa62cYvmQ/X2ARMwV8sJtzwK0CeEwdqanhIwblGYOpcCtRHoqkJqqfWgZnDhqZTwcplCoNNK3FFtVaH53jsqZXY;DT=DI0YLv4MQBGQlusEP4JiLfBCg” *

*-H “Accept-Language: en;q=1.0” *

*-H “Set-Cookie: sid=”"; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/, JSESSIONID=F731CD24B428B6D2ACD6E807E0F50900; Path=/; Secure; HttpOnly" *

*-H “User-Agent: AuthenticationSample/1.0 (PB.AuthenticationSample; build:1; iOS 11.4.0) Alamofire/4.8.0” *

*-H “Accept-Encoding: gzip;q=1.0, compress;q=0.5” *

"https://{domain}/oauth2/v1/authorize?state=staticState&prompt=none&response_type=code&redirect_uri={redirect_uri}&client_id={client_it}&nonce=staticNonce&sessionToken={session_token}&response_mode=query&scope=openid%20offline_access"

RESPONSE-
<NSHTTPURLResponse: 0x610000026bc0> { URL: https://{domain}/oauth2/v1/authorize?state=staticState&prompt=none&response_type=code&redirect_uri={redirect_uri}&client_id={client_id}&nonce=staticNonce&sessionToken={token}&response_mode=query&scope=openid%20offline_access } { Status Code: 302, Headers {
“Cache-Control” = (
“no-cache, no-store”
);
Connection = (
“Keep-Alive”
);
“Content-Language” = (
en
);
“Content-Length” = (
0
);
Date = (
“Thu, 03 Jan 2019 07:32:47 GMT”
);
Expires = (
0
);
“Keep-Alive” = (
“timeout=315, max=200”
);
Location = (
“{redirect_uri}?code=3-AIN-UcpzYa3SRAacOn&state=staticState”
);
P3P = (
“CP=“HONK””
);
Pragma = (
“no-cache”
);
“Public-Key-Pins-Report-Only” = (
“pin-sha256=“jZomPEBSDXoipA9un78hKRIeN/+U4ZteRaiX8YpWfqc=”; pin-sha256=“axSbM6RQ+19oXxudaOTdwXJbSr6f7AahxbDHFy3p8s8=”; pin-sha256=“SE4qe2vdD9tAegPwO79rMnZyhHvqj3i5g1c2HkyGUNE=”; pin-sha256=“ylP0lMLMvBaiHn0ihLxHjzvlPVQNoyQ+rMiaj0da/Pw=”; max-age=60; report-uri=“https://okta.report-uri.io/r/default/hpkp/reportOnly””
);
“Referrer-Policy” = (
“no-referrer”
);
Server = (
nginx
);
“Set-Cookie” = (
“JSESSIONID=1CEF91CF00FBF32F8C9689E5151471DC; Path=/; Secure; HttpOnly”,
“t=purple; Path=/”,
“sid=102F6NMOZE7RPqPgokAREnKIQ; Path=/; Secure”,
“proximity_beb48734015c875160c574c2696a4f68=Ej60dXl725NxOJ0C6TgeKyDHKkJMPV4YpgMHAuXzLsKdpa25jeuSBgIgFOwXy5fnoDY3f7BAlTfOtVjAjTuIdf3xsZXAy6NHP9TKbhxZvLa62cYvmQ/X2ARMwV8sJtzwK0CeEwdqanhIwblGYOpcCtRHoqkJqqfWgZnDhqZTwcplCoNNK3FFtVaH53jsqZXY; Expires=Fri, 03-Jan-2020 07:32:47 GMT; Path=/; Secure”
);
“Strict-Transport-Security” = (
“max-age=315360000”
);
“X-Okta-Request-Id” = (
XC26n56CNkxxHLh8fMVtLQAAAbk
);
“X-Rate-Limit-Limit” = (
40
);
“X-Rate-Limit-Remaining” = (
39
);
“X-Rate-Limit-Reset” = (
1546500777
);
“X-Robots-Tag” = (
none
);
} }

  1. AccessToken-
    REQUEST-
    *$ curl -v *

*-X POST *

*-b “JSESSIONID=1CEF91CF00FBF32F8C9689E5151471DC;proximity_beb48734015c875160c574c2696a4f68=Ej60dXl725NxOJ0C6TgeKyDHKkJMPV4YpgMHAuXzLsKdpa25jeuSBgIgFOwXy5fnoDY3f7BAlTfOtVjAjTuIdf3xsZXAy6NHP9TKbhxZvLa62cYvmQ/X2ARMwV8sJtzwK0CeEwdqanhIwblGYOpcCtRHoqkJqqfWgZnDhqZTwcplCoNNK3FFtVaH53jsqZXY;sid=102F6NMOZE7RPqPgokAREnKIQ;t=purple;DT=DI0YLv4MQBGQlusEP4JiLfBCg” *

*-H “Content-Type: application/x-www-form-urlencoded” *

*-H “Accept-Language: en;q=1.0” *

*-H “Set-Cookie: sid=”"; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/, JSESSIONID=F731CD24B428B6D2ACD6E807E0F50900; Path=/; Secure; HttpOnly" *

*-H “User-Agent: AuthenticationSample/1.0 (PB.AuthenticationSample; build:1; iOS 11.4.0) Alamofire/4.8.0” *

*-H “Accept-Encoding: gzip;q=1.0, compress;q=0.5” *

*-d “grant_type=authorization_code&client_secret={client_secret}&scope=openid%20offline_access&redirect_uri={redirect_uri}&code=3-AIN-UcpzYa3SRAacOn&client_id={client_id}” *

"https://{domain}/oauth2/v1/token"

RESPONSE-

<NSHTTPURLResponse: 0x608000027d00> { URL: https://{domain}/oauth2/v1/token } { Status Code: 403, Headers {
Connection = (
“Keep-Alive”
);
“Content-Length” = (
0
);
Date = (
“Thu, 03 Jan 2019 07:37:55 GMT”
);
“Keep-Alive” = (
“timeout=315, max=200”
);
P3P = (
“CP=“HONK””
);
“Public-Key-Pins-Report-Only” = (
“pin-sha256=“jZomPEBSDXoipA9un78hKRIeN/+U4ZteRaiX8YpWfqc=”; pin-sha256=“axSbM6RQ+19oXxudaOTdwXJbSr6f7AahxbDHFy3p8s8=”; pin-sha256=“SE4qe2vdD9tAegPwO79rMnZyhHvqj3i5g1c2HkyGUNE=”; pin-sha256=“ylP0lMLMvBaiHn0ihLxHjzvlPVQNoyQ+rMiaj0da/Pw=”; max-age=60; report-uri=“https://okta.report-uri.io/r/default/hpkp/reportOnly””
);
Server = (
nginx
);
“X-Okta-Request-Id” = (
“XC270yR-OwFvj3AWjXWOgQAAA5E”
);
“X-Rate-Limit-Limit” = (
40
);
“X-Rate-Limit-Remaining” = (
39
);
“X-Rate-Limit-Reset” = (
1546501085
);
} }

I have tried my different things on this but nothing worked.
The same thing is working in Postman, and also for the android developers in my team.
Need urgent help.

In case anyone else is encountering this issue, the /token endpoint is returning a 403 because a CSRF check is failing. When a cookie is present in your request, Okta will assume it is coming from a browser and perform a CSRF check. In order to resolve this, ensure you are NOT including the ‘sid’ cookie with your request to /token.

If you encounter this issue in Postman, you can resolve it by clearing your cookies there.

3 Likes