Okta-auth-js autoRenew process

Can someone explain how autoRenew works? Does it require users to switch secure routes in order to trigger? We’ve noticed if a user is on a page for a while, ie. filling out a long form, then the autoRenew may not trigger, then the user is kicked out immediately once the session expires. I’m wondering if we’re better off renewing the token ourselves by comparing the current timestamp to the expiration timestamp on the token.

Also, how long is a refresh token good for? Is it only good up until the actual token has expired? In other words, once the token expires, is the refresh token no good?

Are you using AuthJS by itself, or are you using one of our front-end SDKs that are built on top of it (React, Angular, Vue)?

What is your application doing when the user submits their form after the user’s tokens expire?

The token manager has an event listener that will attempt to renew the tokens before they are about to expire. Do you not see this occurring? Are there any errors encountered at this time? Additionally, do you see the same behaviour across various browsers or does this missing auto-renewal only apply to certain browsers/environments?

To answer your last question, the lifetime of the refresh token varies, but it will always have a longer lifetime than the access token issued along with it (otherwise it would kind of defeat the purpose as you surmised). Typically, the refresh token lifetime is 100 days or unlimited, but will be dependent on how your org is configured and which authorization server (issuer) your application is using (as this can be configured via Access Rules).

Thanks for reaching out Andrea.
This behavior seems to be consistent on Safari, but seems to auto renew on Chrome.
I even tested switching SecureRoutes right before the session expired. The token expired seconds after i switched routes, and I got kicked out.
This front end is being built in React. We are using the following okta libraries:
okta-react: 4.1.0
okta-auth-js: 4.6.1
okta-signin-widget: 5.2.2
configuration-validation: 1.0.0

Interesting. So, as I said in Chrome this works fine and I added some logging for the event listeners that you mentioned.
In Safari, I receive this error message:

error.errorCode: login_required, error.description: The client specified not to prompt, but the user is not logged in.

Maybe the renew is executing after the token has been cleared from localStorage?

Can you try disabling the “Prevent Cross-Site Tracking” setting in Safari to see if that resolves the issue?

Safari blocks third-party cookies by default and this can prevent the autoRenew functionality from succeeding as it will rely on being able to access the session cookie Okta sets on your Okta domain to determine if the user is still logged into Okta. This is why you see a “login_required” error, as the autoRenew functionality is trying to silently request tokens without prompting the user. More details about why this occurs here.

If this is the issue you are running into, aside from using a Custom URL domain and hosting your application on the same domain (so that cookies are first party), you can look into using Refresh Tokens instead.

We have a new feature in EA called “Refresh token rotation” which allows SPA applications to request Refresh Tokens from the front end when they use Authorization Code flow with PKCE. You can enable this feature in your org by going to Settings -> Features in the Admin Console and then enabling the “Refresh token” grant type for the SPA application you are using. More details about how to use this feature are available here.

Hi,

Having recently struggled with the token refresh, in react, I’ve scoured this site, and come up with the following recommendations, which work well.

  1. don’t use the refreshToken, unless you want to keep a login alive across multiple days. In our use case, we delegate the life of an Okta accessToken to be the life of the okta session.

  2. make sure to move the token to session storage, and force the refresh to happen before it expires
    const config = {

     issuer: ISSUER,
    
     clientId: clientId,
    
     redirectUri: redirectUri,
    
     scope: SCOPES.split(/\s+/),
    
     pkce: false,
    
     responseType: ['token', 'id_token'],
    
     tokenManager: {
    
         storage: 'sessionStorage',
    
         expireEarlySeconds: 1800,
    
       }
    

    }
    here my accessToken is set to live for 1 hour. At 30 minutes we go get a new one. However this does not always fire, often due to inactivity, or the browser JS engine optomization, so we added:
    a timer to check the auth every couple minutes. This does nothing (network wise) when inside the 30 minutes.
    useEffect(() => {
    //https://github.com/okta/okta-oidc-js/issues/744#
    const interval = setInterval(() => {
    if (isAuthComplete) {
    authService.updateAuthState();
    }
    }, 1000 * 60 * 2);

     return () => {
         clearInterval(interval);
     }
    

    }, );

please note that ant call to authState.IsAuthenticated MUST be protected, as we have witnessed parallel re-auth which causes the okta state to go out of sync. The accessToken is a context stored copy of what is in authState.accessToken, to detect when a new token is received, and store it.

for tracking we decode the tokens:
function parseJwt (token) {
if (token === null || typeof token === “undefined” || typeof token.length === “undefined”) return “”;
var base64Url = token?.split(’.’)[1];
var base64 = base64Url?.replace(/-/g, ‘+’)?.replace(/_/g, ‘/’);
var jsonPayload = decodeURIComponent(atob(base64)?.split(’’)?.map(function© {
return ‘%’ + (‘00’ + c?.charCodeAt(0)?.toString(16))?.slice(-2);
})?.join(’’));

    return JSON.parse(jsonPayload);

  };

var parsedAccessToken = parseJwt(accessToken)
var parsedIdToken = parseJwt(authState.idToken)
console.log(new Date().toLocaleTimeString()+":interceptor Ac Token iat: “+new Date(parsedAccessToken?.iat1000)?.toLocaleTimeString() + " exp: "+new Date(parsedAccessToken?.exp1000)?.toLocaleTimeString()
+ new Date().toLocaleTimeString()+”:interceptor ID Token iat: "+new Date(parsedIdToken?.iat1000 )?.toLocaleTimeString() + " exp: "+new Date(parsedIdToken?.exp1000 )?.toLocaleTimeString());

and log the received, and expire times, as well as detect if the access authState.accessToken has changed. Again I never access the okta objects in my application, only copies.
we store isAuthComplete as a context variable to protect calls to authState.isAuthenticated
when we detect a changed accessToekn we update the copy of authState in our context.
if (isAuthComplete

    && !isGetUserInProgress.current //Wait for getUser to complete

    && authState.accessToken !== accessToken) {

        var parsedAccessToken = parseJwt(accessToken)

        var parsedIdToken     = parseJwt(authState.idToken)

        console.log(new Date().toLocaleTimeString()+":New Ac Token iat: "+new Date(parsedAccessToken?.iat*1000)?.toLocaleTimeString() + " exp: "+new Date(parsedAccessToken?.exp*1000)?.toLocaleTimeString()

                   +" :New ID Token iat: "+new Date(parsedIdToken?.iat*1000    )?.toLocaleTimeString() + " exp: "+new Date(parsedIdToken?.exp*1000    )?.toLocaleTimeString());

        console.log(new Date().toLocaleTimeString()+":New Ac Token: " + authState.accessToken

                  +" :New ID Token: " + authState.idToken);

        if ((typeof authState.accessToken === "undefined" ||  typeof authState.idToken ==="undefined")

        && !isLogoutInProgress.current) {

            isLogoutInProgress.current=true;

            //The user got logged out

            alert(" You have been signed out.  Click OK to Complete Log out...  " );

            HeaderSignOutHandler();

        } else {

            SetAuthState(authState);

        }

}

We detect when the authState becomes undefined, AKA okta session has expired.

we use axios interceptors to both set the accessToken (from a copy in context) and detect 401 errors, as even the above method is not fool-proof.

This is not the fault of the Okta libraries, but of the browser JS optimizations.

I’ve been able to sucessfully continually renew the accessToken for the life of the okta sign on session (tested by being logged into our app, logging out of okta, then waiting for the app to go get a new token.)

This method also has the advantage of accelerating the develop cycle, by changing
expireEarlySeconds: 1800, to just shy of an hour.

Hope this helps.
Ivan