Access token not renewed if expired
Silent renew of the access token is only done if it expires soon, not if it's already expired. It's possible to manually trigger renewal for expired access tokens by
userManager.events.addAccessTokenExpired(function() {
userManager.signinSilent();
})
but I wonder, why it's not done by default? Any specific reason for this, or could it be added by default?
userManager.getUser() should trigger an renew...
OK, but it doesn't do it, if the access token is already expired, only if it's expiring soon. That's my question if this should be changed?!
OK, but it doesn't do it, if the access token is already expired, only if it's expiring soon. That's my question if this should be changed?!
When a look at the code, it does (see "if it's negative, it will still fire" below): https://github.com/authts/oidc-client-ts/blob/1aaeb9441a3235414e78d9f0e889712e4ec88263/src/AccessTokenEvents.ts#L26-L57
You may need to enable logging to console at debugging level to see what is going on with your application:
import { Log } from "oidc-client-ts";
Log.setLogger(console);
Log.setLevel(Log.DEBUG)
Yes, the event AccessTokenExpired is fired, but userManager.getUser() doesn't renew the tokens, if the access token is already expired. Therefore it's mandatory to add the code snippet I've posted to manually run userManager.signinSilent if the access token is expired.
Edit: What I'm doing in detail:
- creating a new user manager with some config that was/is already authenticated,
manger = new UserManager(config) - register to event AccessTokenExpired,
manager.addAccessTokenExpired(callback) - get the user from manager,
manager.getUser(), this returns an object with propertyexpires_atwith a timestamp 16 hours ago. It also calls the callback I've passed at (2), but there is no automatic renew of the access token. I have to adduserManager.signinSilent()to the callback from (2), then it works as expected and the access token is renewed.
However if at (3) the access token isn't already expired, but expires soon, there is no need to register for any events and the access token is renewed automatically.
If setting the log level to DEBUG the only output I get is [UserManager] getUser: user loaded.
Yes, the event
AccessTokenExpiredis fired, butuserManager.getUser()doesn't renew the tokens, if the access token is already expired. Therefore it's mandatory to add the code snippet I've posted to manually runuserManager.signinSilentif the access token is expired.
Have you enabled automaticSilentRenew (is default true since v2.0)? This code is expected to run when "access token expired" happened:
https://github.com/authts/oidc-client-ts/blob/1aaeb9441a3235414e78d9f0e889712e4ec88263/src/SilentRenewService.ts#L47-L63
Yes, automaticSilentRenew is enabled.
I assume that the problem is that it's only listening to AccessTokenExpiring and not also AccessTokenExpired here:
https://github.com/authts/oidc-client-ts/blob/1aaeb9441a3235414e78d9f0e889712e4ec88263/src/SilentRenewService.ts#L23
I assume that the problem is that it's only listening to
AccessTokenExpiringand not alsoAccessTokenExpiredhere
I agree. This code of only doing addAccessTokenExpiring is very old.
~~I guess we can simply extend that and do that as well addAccessTokenExpired after the first register and also we need to remove it again. Can you try this out on your side?~~
This is probably a better way to handle this within SilentRenewService.ts:
...
// this will trigger loading of the user so the expiring events can be initialized
try {
const user = await this._userManager.getUser();
if (user?.expired) {
// we only listen on token expiring, thus request a new token if the current one is expired
await this._tokenExpiring();
}
}
catch (err) {
// catch to suppress errors since we're in a ctor
logger.error("getUser error", err);
}
...
Can you test this?
I've been bit by this as well, one question: after a signinSilent failure (I am getting a 400 - Invalid token from Keycloak) the library doesn't do anything with the user, wouldn't it make sense to somehow clear it?
Am I responsible to do that, and what would be the best API to call? If I do nothing subsequent calls to getUser() simply return the same user, with no indication that the access and/or refresh token are not valid anymore.
After adding
userManager.events.addAccessTokenExpired(async function() {
try {
await userManager.signinSilent();
}
catch (e) {
let user = await userManager.getUser();
// We still have a user with an access token & a refresh token here, should I discard it and how?
}
}
@franck102 in your case, is it possible to redirect the user to authorisation endpoint to reauthenticate?
I feel it is difficult to suggest what best course of action without understanding what exactly causes the signinSilent to fail.
@pamapa What is the usual process when testing changes like that? I would like to help with the testing as I am affected by this problem as well