Okta.com/oauth2/v1/token issues

So I have add Okta integration to my application. It’s just trying to read the /api/v1/users endpoint and get that info to create user in my app. I have created a OpenID application on my Okta trial version to test things out.

Once I try to make a request to get the users as I understand , the flow is → I try getting users → then the request is first going to the okta.com/oauth2/v1/authorize endpoint to authorized my request and for that it goes to the okta.com/oauth2/v1/token where I make a request to update my refresh_token if it expired?

        response = session.request(
            method='POST',
            url=f'{self.api_url}/oauth2/v1/token',
            data={
                'grant_type': 'refresh_token',
                'scope': self.auth_credentials['scope'],
                'refresh_token': self.auth_credentials['refresh_token'],
            },
            headers={
                'Accept': 'application/json',
            },
            auth=HTTPBasicAuth(client_id, client_secret),
        )

Once my refresh token is refreshed this would make a request to authorize to see if the credentials are valid and the request can be continued to the desired users endpoint, but it fails to authorize, because Im unable to get a new refresh_token?

I get this :slight_smile:

auth_info ->  Bearer authorization_uri="http://trial-9416713.okta.com/oauth2/v1/authorize", realm="http://trial-9416713.okta.com", scope="okta.users.read.self", error="invalid_token", error_description="The token has expired.", resource="/api/v1/users"

So since the token has expired im make a new request to the /oauth2/v1/token to get a new token and then make the same previous request to the users endpoint, but it fails with error:

HTTPError('400 Client Error: Bad Request for url: https://trial-9416713.okta.com/oauth2/v1/token')")

this is the response headers coming from this failed request:

{'Date': 'Fri, 23 Feb 2024 12:49:22 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Server': 'nginx', 'x-okta-request-id': '01beed0bb9383708157747fc0abaf71b', 'x-xss-protection': '0', 'p3p': 'CP="HONK"', 'set-cookie': 'sid="";Version=1;Path=/;Max-Age=0, autolaunch_triggered=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/, JSESSIONID=07535AF546B6C4A827284A4F1F3012A0; Path=/; Secure; HttpOnly', 'content-security-policy': "default-src 'self' trial-9416713.okta.com *.oktacdn.com; connect-src 'self' trial-9416713.okta.com trial-9416713-admin.okta.com *.oktacdn.com *.mixpanel.com *.mapbox.com *.mtls.okta.com trial-9416713.kerberos.okta.com *.authenticatorlocalprod.com:8769 http://localhost:8769 http://127.0.0.1:8769 *.authenticatorlocalprod.com:65111 http://localhost:65111 http://127.0.0.1:65111 *.authenticatorlocalprod.com:65121 http://localhost:65121 http://127.0.0.1:65121 *.authenticatorlocalprod.com:65131 http://localhost:65131 http://127.0.0.1:65131 *.authenticatorlocalprod.com:65141 http://localhost:65141 http://127.0.0.1:65141 *.authenticatorlocalprod.com:65151 http://localhost:65151 http://127.0.0.1:65151 https://oinmanager.okta.com data: data.pendo.io pendo-static-5634101834153984.storage.googleapis.com pendo-static-5391521872216064.storage.googleapis.com; script-src 'unsafe-inline' 'unsafe-eval' 'self' trial-9416713.okta.com *.oktacdn.com; style-src 'unsafe-inline' 'self' trial-9416713.okta.com *.oktacdn.com; frame-src 'self' trial-9416713.okta.com trial-9416713-admin.okta.com login.okta.com com-okta-authenticator:; img-src 'self' trial-9416713.okta.com *.oktacdn.com *.tiles.mapbox.com *.mapbox.com data: data.pendo.io pendo-static-5634101834153984.storage.googleapis.com pendo-static-5391521872216064.storage.googleapis.com blob:; font-src 'self' trial-9416713.okta.com data: *.oktacdn.com fonts.gstatic.com; frame-ancestors 'self'", 'x-rate-limit-limit': '300', 'x-rate-limit-remaining': '299', 'x-rate-limit-reset': '1708692622', 'cache-control': 'no-cache, no-store', 'pragma': 'no-cache', 'expires': '0', 'accept-ch': 'Sec-CH-UA-Platform-Version', 'x-content-type-options': 'nosniff', 'Strict-Transport-Security': 'max-age=315360000; includeSubDomains'}

Could someone help me out with this issue as Im not able to make it work properly, am I missing something in my setup or something else is making my Okta integration fail once I try getting users data?

These are the screenshots of the setup:


Screenshot 2024-02-23 at 14.52.59

Hi @Robertas,

Once you interactively login via the okta default / root authorization server /authorize URL and enter your credentials, you’ll get an authorization code. (I’m assuming this is in a browser and you’re logging in as a person). You need to specify the Okta scopes you want such as okta.users.read in the /authoize request.

You swap the authcode for a token at the /token endpoint.

The scopes in your request need to match what you’ve configured in okta for your app (okta.users.read).

You’ve ticked too many grant types (know which flow you’re going for). This is OK while learning but could be bad in prod.

Cheers,
Adrian

Is this a client credentials flow (you’re authenticating as a system with a clientid and secret)? If so, the flow is slightly different.
There’s only a single call to /authorize where you specify the okta scopes you would like e.g. okta.users.read.
The token you get back you can use with the /users API adding the token in the bearer header.

Cheers,
Adrian

hey @abole , but I do include scopes in my requests. Im not able to understand why this is not working properly. the flow is simple I make a request to /users endpoint to get all the users so the question is, when making a request to users endpoint and I get an unauthorized error that says: error="invalid_token", error_description="The token has expired." what flow should it be? Now once I face such error I just make a request to token` endpoint with params :

        response = session.request(
            method='POST',
            url=f'{self.api_url}/oauth2/v1/token',
            data={
                'grant_type': 'refresh_token',
                'scope': self.auth_credentials['scope'],
                'refresh_token': self.auth_credentials['refresh_token'],
            },
            headers={
                'Accept': 'application/json',
            },
            auth=HTTPBasicAuth(client_id, client_secret),
        )

And this is currently crashing every try once i face that expired token error. Should I just make a request to authorize and then once getting the new access_token re-do the request to users or I should be making some other request? like to get token again?

What scopes are you specifying in self.auth_credentials[‘scope’]?
If you get a token back, paste it into jwt.io and check its valid.

this is the output → openid profile email okta.users.read offline_access

I don’t get any token back, once I do the request to token I get error:

response.json () => ```
{‘error’: ‘invalid_grant’, ‘error_description’: ‘The refresh token is invalid or expired.’}


response headers =>  ```
{'Date': 'Mon, 26 Feb 2024 12:23:39 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Server': 'nginx', 'x-okta-request-id': '1c319af9eec7901ec07baed2a6801257', 'x-xss-protection': '0', 'p3p': 'CP="HONK"', 'set-cookie': 'sid="";Version=1;Path=/;Max-Age=0, autolaunch_triggered=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/, JSESSIONID=FA204739571325688F47D55E388314FB; Path=/; Secure; HttpOnly', 'content-security-policy': "default-src 'self' trial-9416713.okta.com *.oktacdn.com; connect-src 'self' trial-9416713.okta.com trial-9416713-admin.okta.com *.oktacdn.com *.mixpanel.com *.mapbox.com *.mtls.okta.com trial-9416713.kerberos.okta.com *.authenticatorlocalprod.com:8769 http://localhost:8769 http://127.0.0.1:8769 *.authenticatorlocalprod.com:65111 http://localhost:65111 http://127.0.0.1:65111 *.authenticatorlocalprod.com:65121 http://localhost:65121 http://127.0.0.1:65121 *.authenticatorlocalprod.com:65131 http://localhost:65131 http://127.0.0.1:65131 *.authenticatorlocalprod.com:65141 http://localhost:65141 http://127.0.0.1:65141 *.authenticatorlocalprod.com:65151 http://localhost:65151 http://127.0.0.1:65151 https://oinmanager.okta.com data: data.pendo.io pendo-static-5634101834153984.storage.googleapis.com pendo-static-5391521872216064.storage.googleapis.com; script-src 'unsafe-inline' 'unsafe-eval' 'self' trial-9416713.okta.com *.oktacdn.com; style-src 'unsafe-inline' 'self' trial-9416713.okta.com *.oktacdn.com; frame-src 'self' trial-9416713.okta.com trial-9416713-admin.okta.com login.okta.com com-okta-authenticator:; img-src 'self' trial-9416713.okta.com *.oktacdn.com *.tiles.mapbox.com *.mapbox.com data: data.pendo.io pendo-static-5634101834153984.storage.googleapis.com pendo-static-5391521872216064.storage.googleapis.com blob:; font-src 'self' trial-9416713.okta.com data: *.oktacdn.com fonts.gstatic.com; frame-ancestors 'self'", 'x-rate-limit-limit': '300', 'x-rate-limit-remaining': '299', 'x-rate-limit-reset': '1708950279', 'cache-control': 'no-cache, no-store', 'pragma': 'no-cache', 'expires': '0', 'accept-ch': 'Sec-CH-UA-Platform-Version', 'x-content-type-options': 'nosniff', 'Strict-Transport-Security': 'max-age=315360000; includeSubDomains'}

@abole should I do a request to authorize before makinga request to token? or there should be no need to make requests to token just to make a request to authorize get new access_token and make request then?

Can you see the call its actually making to /authorise and /token? Looks like the requests are incorrect.

Chrome dev tools > network tab is a good place to find this.

100%. Call /authorise first then /token.

Your call to /authorize should look something like this for auth code with pkce:

https://yourdomain.okta.com/oauth2/ v1/authorize?client_id=YOURCLIENTID&response_type=code&response_mode=query&scope= openid profile email okta.users.read&redirect_uri=REDIRECTURI &state=RANDOMSTATEVALUE1234&nonce=8faa7f50-11bd-4a16-9656-2018529e6f31&code_challenge_method=S256&code_challenge=SWdNSVyVZgF_mgPf9xVSyvilVj6ZUmpI2s5-gHQALDg

For testing I use postman echo with displays what comes back in the browser and I can copy and paste the authcode from here.

https://postman-echo.com/get?callback=true

Call to /token will be something like:

https:// yourdomain.okta.com/oauth2/v1/token

with body params of:

grant_type authorization_code

client_id YOURCLIENTID

redirect_uri REDIRECTURI

code AUTHORIZATION CODE FROM /authorize

code_verifier CODEVERIFIER

Im not doing requests from a browser, this a crontab task that starts running once I hit a button. the task’s purpose is to sync users. I’ve used oauth to login to my okta instance that I’ve created for testing, once my okta has a user set that can get the access to users (there’s only 1 user that is the one Im using to make requests) it works once the token is valid, when the token is not valid anymore, i cannot make any requests to refresh the token or alike.

Then you want to do client credentials flow to the /token endpoint. No calls to /authorize in this flow.

Here’s the Okta guide https://developer.okta.com/docs/guides/implement-grant-type/clientcreds/main/

Postman screenshot

You can untick every grant type that isn’t Client Credentials.
Don’t forget to protect your client secret in prod.

You need to make another call to the /token endpoint. Your app / script triggered by the cron tab needs to detect the unauthorised call (or introspect the token and see that its expired) and make another call to /token with clientid and secret to get a new token. That’s pretty much it.

yes, that’s what im doing :slight_smile:

    def request(self, method: str, url: str, **kwargs):
        try:
            return super().request(method, url, **kwargs)
        except AccessDenied as ex:
            if hasattr(ex, 'args') and hasattr(ex.args[0], 'response'):
                response = ex.args[0].response
                auth_info = response.headers.get('www-authenticate')
                if (
                    auth_info and
                    'error="invalid_token"' in auth_info and
                    'error_description="The token has expired."' in auth_info
                ):
                    self.refresh_token()
                    return super().request(method, url, **kwargs)
            raise

if the request to any endpoint get accessDenied error with key words related to token expiration just like I’ve pased above, the refresh_token() method makes a request to token endpoint refreshes it and makes the same request again once refresh_token() finishes:

    def refresh_token(self):
        client_id = self.service_credentials['client_id']
        client_secret = self.service_credentials['client_secret']

        session = self.session
        retries = Retry(
            total=self.retry_max_count,
            backoff_factor=self.retry_backoff_factor,
            allowed_methods=self.retry_allowed_methods,
            status_forcelist=self.retry_status_codes,
            raise_on_status=False,
        )
        session.mount('https://', HTTPAdapter(max_retries=retries))

        response = session.request(
            method='POST',
            url=f'{self.api_url}/oauth2/v1/token',
            data={
                'grant_type': 'refresh_token',
                'scope': self.auth_credentials['scope'],
                'refresh_token': self.auth_credentials['refresh_token'],
            },
            headers={
                'Accept': 'application/json',
            },
            auth=HTTPBasicAuth(client_id, client_secret),
        )
        if response.status_code != 200:
            headers = response.headers
            json_response = response.json()
            logger.error(
                f'Failed to refresh Okta token: {json_response} | {headers}',
                exc_info=HTTPError(),
            )
        response.raise_for_status()
        self.auth_credentials = response.json()
        if self.on_refresh_token:
            self.on_refresh_token(self.auth_credentials)

I see that you are doing a different request with a specific authorizationServerId in the link, im not using that, what is this needed if I only have 1 default authorizartion server:

Apart from this, I see that you are including redirect_uri and in general using different params compared to mine?

Also, I cannot use such grant type it seems , since : {'error': 'invalid_scope', 'error_description': "Cannot request 'openid' scopes using client credentials."}

hey @abole , I still don’t understand, since I only do a request to my default auth server, I didn’t specified the authServerId in the url (not sure if this is needed for my OCID app) also, I’ve tried some different grant types and different tokens (id_token, access_token, refresh_token) and none of those work with pretty much the same error, either the grant type is bad and I see :

{'error': 'invalid_grant', 'error_description': 'The authorization code is invalid or has expired.'}

{'error': 'invalid_grant', 'error_description': 'The refresh token is invalid or expired.'}

Does my request seem to be bad somehow, since I don’t even get unauthorized error, it’s just coming as a bad request in the first place…

HTTPError: 400 Client Error: Bad Request for url: 
https://trial-9416713.okta.com/oauth2/v1/token

The default authorization service listens on /oauth2/default as you can see in your screenshot. Your token endpoint should be: https://trial-9416713.okta.com/oauth2/default/v1/token.
Omitting the /default/ instead uses the Okta Org authorization service, which is different from the default custom authorization service. The Org authorization service is primarily used for OIDC SSO. See here: Authorization servers | Okta Developer

In addition, you cannot use openid scopes with the client credentials flow. You are not expecting to get back an ID token for the service, instead an access token that the service can use to interact with Okta. Remove the OIDC scopes and leave the okta.users.* scopes.

Before all of this though, I think we need to backup and look at the original request you are making, as it sounds like you are getting an initial access token that works, until it expires the first time. Is that also using client credentials, or is it using something like the authorization code grant which authenticates your Okta user (as part of /authorize call)? If so, are you receiving a refresh token? From the setup I can see, it appears you would expect to get a refresh token during the initial user authorization, and then provide the refresh token to the service to continue to request access tokens on behalf of the user.

1 Like

hey @mcampbell

Yes, the flow is that I add Okta and once it’s added a user sign-ins into that Okta. I save that users info (the user should be the one that can access all the tings in Okta for the most part) once he is signin into Okta, I use /authorize endpoint and once this user authorizes, I save the tokens and use them just for my server-to-server communication. which is as I’ve mentioned just a crontab task that syncs once per 24hours. Once it starts the sync I only need users, so I make a request to /users and once making request to users I check if I get that expired token error, if I see it I make a request to token to get a new refresh token with included OIDC scopes in to scope param for token endpoint.

And I only include client credentials only for /authorize or /token endpoints.

So let me try removing the OIDC scopes and see if that would help to get the new refresh token.

Once I was making request to /token I’ve sent such scope → openid profile email okta.users.read offline_access I’ve removed the openid and this didin’t work Im still seeing:

{'error': 'invalid_grant', 'error_description': 'The refresh token is invalid or expired.'}

I’ve tried adding the default to my /token request url and it also didn’t do much.

Can you ensure you are following the steps here to use the refresh token:

You should be getting that refresh token as part of the original token request (user context). You are able to include ‘openid’ again as a scope if you want to refresh the ID token, otherwise omitting it will only provide an access token (and refresh token if offline_access is requested as well).