Unable to refresh accessToken with JS and Spring Boot

The scenario is this;

  • I have a web application (JS / jQuery), a Spring-Boot backend and a custom domain hosting the sign-in widget.
  • My web app has a landing page and several other pages that require authentication, one of which uses websockets for communicating with the server.
  • When a user accesses one of the authentication-required pages, they are redirected to the sign-in and, once signed-in, the bounce back to the original page.
  • The okta.oauth2.scopes=profile, email, openid, offline_access
  • Each time a page is accessed the JS client requests session information from my back-end via a REST request. If there is an @AuthenticationPrincipal OidcUser then the user details (username, email etc) is returned. If there is a OAuth2AuthorizedClient available (taken from the REST method receiving a valid OAuth2AuthenticationToken), then the OAuth2AccessToken and OAuth2RefreshToken details are also returned to the client.
  • When the client-side user accesses the websocket communication page, the accessToken value is added to the stompClient headers ("Bearer [accessToken value]"), which the server validates and, if valid, returns the required websocket message.

This all works fine, until the accessToken expires after x amount of time and the server-side websocket validation rejects the token and stops sending messages to the client. It is important to note that the websocket connection needs to be maintained for 2-8 hours.

In this scenario, the user is still known to the client as the oauth scopes includes offline_access, but the token has expired.

My question is; How do I silently refresh the accessToken?

Problems:
I have tried to work around this for two days, but am still having no joy.
On the client-side (using https://global.oktacdn.com/okta-auth-js/4.0.2/okta-auth-js.min.js) I have tried creating

authClient = new OktaAuth({
issuer: ‘[my custom domain]/oauth2/default’,
clientId: ‘[my client id]’,
redirectUri: ‘[my web domain]/authorization-code/callback’,
tokenManager: {
storage: ‘sessionStorage’,
autoRenew: true
},
cookies: { secure: true }
});

and then using the following call when the new information is returned from the server

authClient.tokenManager.add(‘accessToken’, at );

as the documentation suggests that this will enable autoRenew, but it doesn’t. More-over, if I use

authClient.tokenManager.hasExpired(at);

this always returns true, even if the token has expired!
I tried using localStorage and sessionStorage in the client configuration, but it makes no difference. If I try to load the accessToken with

await authClient.tokenManager.get(‘accessToken’).then( token => {});

it appears that client tries to attempt a refresh, but it errors with ‘401 Not authorized’.
Looking at the API guide, it appears that auto-renew relies on third-party cookies, which is possibly why this doesn’t work.

I have tried requesting a refresh of the token on the server-side using a POST to the

[my custom login domain]/oauth2/default/v1/token

endpoint with the settings

client_id = [my client id]
refresh_token = [refresh token]
grant_type = “refresh_token”
authorization = Base64( [clientId] : [clientSecret] )
scopes = [oauth scopes]
redirect_uri = [redirectUrl]

but this returns ‘Client not authorised’.

Many other applications must refresh their access tokens, so what am I missing?!
Currently I have simply increased my accessToken lifetime to 1 day (the maximum), but this doesn’t feel like the right solution…

I think, when you do request from your backend, the authorization should be sent in the header. It’s not quite clear from your post, if you do the same way.

Also you can try to subscribe to ‘expired’ event and get() token in it. Make sure that your access token lifetime less or equal your Okta session lifetime, as this "auto"Renew works based on the premise that your session is still active. It doesn’t use refresh_token

Thanks for getting back phi1ipp, although I’m not sure I understand…

I have changed my client-side code to

  // Build the client
	AUTH_CONFIG.issuer = data.tokens.issuer;
	AUTH_CONFIG.clientId = data.tokens.clientId;
	AUTH_CONFIG.redirectUri = data.tokens.redirectUrl;
	authClient = new OktaAuth(AUTH_CONFIG);
	
	// Triggered when a token has expired
	authClient.tokenManager.on('expired', function (key, expiredToken) {
	  console.log('Token has expired - reloading...');
	  accessTokenValue = '';
	  loadAccessToken();
	});

async function loadAccessToken() {
var fullToken = await authClient.tokenManager.get(‘accessToken’)
.then( token => {
// If access token exists, return it
if (token) {
if (authClient.tokenManager.hasExpired(token)) {
authClient.tokenManager.renew(‘accessToken’).then(newToken => {
console.log(‘Renewed’);
return newToken;
});
} else {
return token;
}
}

But in the console I am getting;

Token has expired - reloading…
POST https://[custom domain]/oauth2/default/v1/token >> 401 (Unauthorized)

On the back-end the body of the POST to the token endpoint includes

authorization = Base64.encode( [clientId] +“:”+ [clientSecret] );

Should I be doing something extra on the backend like setting the authorization as a header value, compared to in the POST body. On the front-end there are no options for setting headers.

Do I need to do anything extra to initiate a session as you mention? I have presumed logging in creates a session automatically in the browser and as the scopes include offline_access the session should only expire after 60 days.

Thanks

Double check what your Okta session lifetime is. It should be longer than access_token expiration time. Only in this case it will work. If you have Okta Admin rights, then adjust either one, or both.

Check this:- OpenID Connect & OAuth 2.0 API | Okta Developer

That’s wrong assumption. The session will expire according to authentication policy. But as you have refresh_token you can initiate a refresh of your access_token as long as your refresh_token is active, w/o having Okta user session