401 Client error: Unauthorized for url: https://{org-url}/oauth2/v1/token

I’m trying to create a streamlit app and using the code referenced in this article to create a login gated streamlit app.

I can successfully get a code from the /oauth2/v1/authorize endpoint. However, when I try to get an access token from the /oauth2/v1/token endpoint, I get the error: “401 Client Error: Unauthorized for url: https://{org-url}/oauth2/v1/token”

It’s a “Web Application” in Dashboard. The authentication method is client_secret_post. I’m using endpoints that are /oauth2/v1/authorize, /oauth2/v1/token and /oauth2/v1/keys because that’s what I see in OpenID: https://${org-url}/.well-known/openid-configuration OAuth: https://${org-url}/.well-known/oauth-authorization-server

Code snippet is below:

auth_code = base64.b64encode(f"{config['client_id']}:{config['client_secret']}".encode("utf-8")).decode('ascii')
        theaders = {
            # 'accept': 'application/json',
            'Authorization': f'Basic {auth_code}',
            'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
        }
        tdata = {
            'client_id': config['client_id'],
            'client_secret': config['client_secret'],
            'grant_type': 'authorization_code',
            'redirect_uri': config['redirect_uri'],
            'code': code,
            'state': state,
            'scope': config['scope'],
        }
        ret = requests.post(config["token_endpoint"], headers=theaders, data=urlencode(tdata).encode("utf-8"))

On the Okta Dashboard logs, the error seen is:
Screenshot from 2023-11-17 15-32-47

Imo I’m using the right authorization code. It’s supposed to be 64 bit encoded client_id:client_secret and I followed the steps to generate it using linux command line before I used python code to generate it as seen in code.

Any idea what’s wrong? How can I fix this? Let me know if more information needed.

I think you might be confused about the authorization_code. In Authorization Code flow, the authorization_code is returned to your application from the IdP (Okta) when you make a successful /authorize request. You must make this authorize request first to receive the authorization_code.

More details about how to complete Authorization Code flow can be found in our guide: Implement authorization by grant type | Okta Developer

Hi!
I’m sending a request to /authorize endpoint first. I get a code from there.

state_parameter = string_num_generator(15)
    query_params = urlencode(
        {'client_id': config['client_id'], 'response_type': 'code',
         'scope': config['scope'], 'redirect_uri': config['redirect_uri'],
         'state': state_parameter})
    request_url = f"{config['authorization_endpoint']}?{query_params}"
    st.markdown(f'<a href="{request_url}" target="_self">{label}</a>', unsafe_allow_html=True)
    st.stop()

where config[‘authorization_enpoint’] is "https://{org-url}/oauth2/v1/authorize”.

Then a code is sent back as url parameter, which I get using

code = st.experimental_get_query_params()['code']
state = st.experimental_get_query_params()['state']

I use these code and state parameters to get a token, which is the code in the original post.

        code = st.experimental_get_query_params()['code'] 
        state = st.experimental_get_query_params()['state']
        auth_code = base64.b64encode(f"{config['client_id']}:{config['client_secret']}".encode("utf-8")).decode('ascii')
        theaders = {
            'Authorization': f'Basic {auth_code}',   #This auth_code is client_id:client_secret as encoded above
            'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
        }
        tdata = {
            'client_id': config['client_id'],
            'client_secret': config['client_secret'],
            'grant_type': 'authorization_code',
            'redirect_uri': config['redirect_uri'],
            'code': code,  #This is the code we get after redirect_uri from okta sign-in page 
            'state': state,  #This is the state we get after redirect_uri from okta sign-in page 
            'scope': config['scope'],
        }
        try:
        
            ret = requests.post(config["token_endpoint"], headers=theaders,  data=urlencode(tdata).encode("utf-8"))

To summarize, following the steps at Implement authorization by grant type | Okta Developer, I’m calling the /authorize endpoint, am getting redirected to login, am able to login and get a ‘code’, then this code is passed to /token endpoint to get a token.

At the end, the error that is shown is the one in first post.

The error you see implies the authorization code is invalid. Are you reusing the code or trying the request multiple times? Does the flow work for you if you complete it manually (e.g. using Postman or Curl for the /token request)? Do you see any difference between the code you receive back from the /authorize request with the one that you see being sent to the /token endpoint?

I took your suggestion and tried it out manually using curl. The request works and I get an access token. Interestingly, when I try it using curl, I use the following format.

https://{org-url}/oauth2/v1/authorize?
   client_id=<client_id>&
   response_type=code&scope=openid&
   redirect_uri=http%3A%2F%2Flocalhost%3A8501&
   state=state-296bc9a0-a2a2-4a57-be1a-d0e2fd9bb001

curl --request POST \
  --url https://{org-url}/oauth2/v1/token \
  --header 'accept: application/json' \
  --header 'authorization: Basic {base 64 encoded client_id:client_secret}' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data 'grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A8501&code=<code>&state=state-296bc9a0-a2a2-4a57-be1a-d0e2fd9bb001'

The client_id:client_secret is only in header for /token endpoint request. There is no client_id and client_secret in the ‘data’ for the request. I tried and that gave me “too many client credentials” error.

So, since I now had an example request that i can use to fix my code, I tried it this way. However,

When I removed “client_id” and “client_secret” from my /token endpoint request ‘data’ body, I get the error “400 Client error: Bad request for url”. So, I followed the documentation here for client_secret_post authentication and once again added “client_id” and “client_secret” back to the ‘data’.

As a result, I once again get the ‘401 error’, as mentioned in first post.

Tl;dr Yes, the code passed from /authorize endpoint is same as the one being sent to the token endpoint. Next, I tried the CURL requests manually, found I do get the access token that way. But when I try it in code, I get “400 client error”. So I need to add extra ‘data’ for client_id and client_secret in request. Manually, that gives me the error “too many client credentials”, in code it gives me the error" 401 client error".

hm… glad to hear it works doing it manually at least. and yeah, the client_id:client_secret should be included in either the header or the body of the request, but not both (hence the too many client credentials error).

You may want to check what the raw request that gets made actually looks like, see if it matches what you see when you use curl. There’s gotta be something different…

Just an update. I was able to fix the issue by… well, there was nothing with the code. However, we toggled the Issuer to “Okta URL” and it started working. Then, we toggled it back to “Dynamic” to confirm the Issuer was causing failure but no. The token request was now working successfully even with “Dynamic” Issuer.

So. I don’t know what was causing our /token request to fail but it’s not now. With no changes in code (except, ofc, removing the client id and secret from request “data”. We didn’t get “too many client credentials” error before. We do now. Which means it works as expected, yay!).

Just wanted to put it here, to close the issue. It’s fixed. Everything works. Thanks for your support. It helped me understand the login flow better, for future debugging!

1 Like

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