Version
21.0.0
Please provide a link to a minimal reproduction of the bug
No response
Please provide the exception or error you saw
No exception is thrown. The periodic token expiry check silently stops working. After a successful checkAuthIncludingServer call (which authenticates via iframe silent renew using an existing SSO session), the shouldStartPeriodicallyCheckForConfig method always returns false because isCodeFlowInProgress reads true from storage indefinitely. Tokens are never silently renewed after that point.
Steps to reproduce the behavior
1. Configure the library with silentRenew: true and useRefreshToken: false (iframe-based silent renew).
2. Call authorize() — the user is redirected to the Identity Provider. storageCodeFlowInProgress is set to true.
3. The user authenticates at the IdP (SSO session is established) but does not return via the expected authorization callback URL (e.g. navigates directly back to the app root).
4. The app calls checkAuthIncludingServer(). Since there are no local tokens, checkAuthWithConfig returns isAuthenticated: false.
5. forceRefreshSession is called and successfully retrieves tokens via iframe silent renew (SSO session exists at the IdP).
6. The user is now authenticated with valid tokens in storage.
7. Observe that startTokenValidationPeriodically never triggers a token refresh — shouldStartPeriodicallyCheckForConfig always returns false.
A clear and concise description of what you expected to happen.
After checkAuthIncludingServer successfully authenticates the user via silent renew, the periodic token check should work normally and silently renew tokens before they expire, just as it would after a regular checkAuth callback flow.
Additional context
The root cause is that resetCodeFlowInProgress is only called inside CodeFlowCallbackService.authenticatedCallbackWithCode (code-flow-callback.service.ts:32), which is only reached when the current URL contains an authorization code (i.e. [isCallback === true]). When the code flow is abandoned and checkAuthIncludingServertakes the forceRefreshSessionpath instead, resetCodeFlowInProgress is never called, leaving storageCodeFlowInProgress = true in storage permanently.
Contrast this with isSilentRenewRunning which has a built-in timeout mechanism (silentRenewTimeoutInSeconds) that self-heals if the process gets stuck. isCodeFlowInProgress has no equivalent safeguard.
Suggested fix — reset the flag in checkAuthWithConfig when no callback is detected, since that implies any previously started code flow was abandoned:
// check-auth.service.ts — checkAuthWithConfig
const isCallback = this.callbackService.isCallback(currentUrl, config);
if (!isCallback) {
this.flowsDataService.resetCodeFlowInProgress(config);
}
This is the minimal, defensive fix: if the app loads without a callback URL, there is no in-flight code flow to protect, so the flag should always be cleared.
Version
21.0.0
Please provide a link to a minimal reproduction of the bug
No response
Please provide the exception or error you saw
Steps to reproduce the behavior
A clear and concise description of what you expected to happen.
Additional context
The root cause is that
resetCodeFlowInProgressis only called insideCodeFlowCallbackService.authenticatedCallbackWithCode(code-flow-callback.service.ts:32), which is only reached when the current URL contains an authorization code (i.e. [isCallback === true]). When the code flow is abandoned andcheckAuthIncludingServertakes theforceRefreshSessionpath instead,resetCodeFlowInProgressis never called, leaving storageCodeFlowInProgress = true in storage permanently.Contrast this with isSilentRenewRunning which has a built-in timeout mechanism (silentRenewTimeoutInSeconds) that self-heals if the process gets stuck. isCodeFlowInProgress has no equivalent safeguard.
Suggested fix — reset the flag in
checkAuthWithConfigwhen no callback is detected, since that implies any previously started code flow was abandoned:This is the minimal, defensive fix: if the app loads without a callback URL, there is no in-flight code flow to protect, so the flag should always be cleared.