Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token Renewal Fails in PWA (React) on iPad, Leading to User Disconnection or Stuck Unauthenticated State #1575

Open
morfees opened this issue Mar 1, 2025 · 7 comments
Labels

Comments

@morfees
Copy link

morfees commented Mar 1, 2025

Describe the bug

Token renewal sometimes fails in a PWA running on an iPad, even though refresh tokens are configured with the offline_access scope. When this happens, the refresh token is deleted, leaving users either disconnected or unknowingly stuck in an unauthenticated state.

Reproduction Steps?

  1. Initial Setup
    • Configure refresh tokens with the offline_access scope enabled.
    • Initially, set autoRenew to true. When the token expires offline, users get disconnected upon reconnecting.

  2. Switch to Manual Renewal
    • Set autoRenew to false and listen for token expiration events.
    • Call renewTokens() when the event oktaAuth.tokenManager.on("expired") fires and navigator.onLine is true.
    • Some users experience token renewal failure with this error, possibly due to network issues.
    { "name": "AuthApiError", "errorSummary": "Load failed" }
    • Some affected users are in areas with very poor network connections.

  3. Critical Issue: Refresh Token Deletion
    • When renewTokens() fails, the refresh token is deleted, despite its 90-day lifetime and daily reissuance, this results in two cases:
    - With third-party cookies blocked: /authorize request fails with the error :
    { "resp": null, "name": "OAuthError", "errorCode": "login_required", "errorSummary": "The client specified not to prompt, but the user is not logged in.", "error": "login_required", "error_description": "The client specified not to prompt, but the user is not logged in." }
    - With third-party cookies enabled: The renewal sometimes works sometimes not.
    • If a user closes the app indefinitely and later reopens it, failing to fetch encryption key data with a 401 error prevents the app from loading.

SDK Versions

7.10.1

Additional Information?

Any insights or guidance on how to improve the token renewal process would be greatly appreciated. Thank you !

@morfees morfees added the bug label Mar 1, 2025
@jaredperreault-okta
Copy link
Contributor

jaredperreault-okta commented Mar 3, 2025

I think setting autoRemove: false may fix your issue
https://github.com/okta/okta-auth-js?tab=readme-ov-file#autoremove-1

@morfees
Copy link
Author

morfees commented Mar 4, 2025

Thanks for the reply. Then thing is autoRemove is already set to false :

  issuer,
  clientId,
  redirectUri,
  scopes,
  pkce: true,
  tokenManager: {
    autoRenew: false,
    autoRemove: false,
  },
}

@jaredperreault-okta
Copy link
Contributor

https://github.com/okta/okta-auth-js/blob/master/lib/oidc/renewTokensWithRefresh.ts#L62

If autoRemove: false is turned off, the only reason the refresh token would be removed from storage is if the following error is returned by the refresh attempt:

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

Have you observed this error in console logs or in your Okta Admin Sys Logs?

@morfees
Copy link
Author

morfees commented Mar 5, 2025

Yes, I do see this in my monitoring tool. However, the issue is that all users are required to log out and log back in every day, which means a new refresh token is issued daily. So how can it be invalid or expired?

{
    "resp": {
      "responseText": "{\"error\":\"invalid_grant\",\"error_description\":\"The refresh token is invalid or expired.\"}",
      "status": 400,
      "headers": {
        "cache-control": "no-cache, no-store",
        "content-type": "application/json",
        "expires": "0",
        "pragma": "no-cache"
      },
      "responseType": "json",
      "responseJSON": {
        "error": "invalid_grant",
        "error_description": "The refresh token is invalid or expired."
      }
    },
    "name": "OAuthError",
    "errorCode": "invalid_grant",
    "errorSummary": "The refresh token is invalid or expired.",
    "error": "invalid_grant",
    "error_description": "The refresh token is invalid or expired.",
    "tokenKey": "accessToken"
  }

My Code:

I’ve removed the listener for the expiration event because the app is used in locations with poor internet connectivity. When renewal failed, the tokens were getting wiped every time. Instead, I now renew the token only after receiving a 401, ensuring the user has internet access before attempting renewal.

I also added a "Load fail" condition, which improves the situation but still feels like a bit of a hack:

const handleAccessTokenRenewError = async (
  error: unknown,
  existingTokens: Tokens
) => {
  if ((error as AuthApiError)?.errorSummary === "Load failed") {
    // If the user is offline or has poor internet, restore the existing tokens.
    await oktaAuth.tokenManager.setTokens(existingTokens)
  }

  log(...)

  throw error
}

export const renewAccessToken = async (): Promise<void> => {
  isRenewingTokens = true

  const existingTokens = await oktaAuth.tokenManager.getTokens()

  try {
    await oktaAuth.tokenManager
      .renew("accessToken")
      .catch((error) => handleAccessTokenRenewError(error, existingTokens))
      .then(() => logWithTime("Tokens renewed"))
  } finally {
    isRenewingTokens = false
  }
}

@jaredperreault-okta
Copy link
Contributor

jaredperreault-okta commented Mar 5, 2025

the issue is that all users are required to log out and log back in every day

Is code within your app signing users out? Are you certain a new refresh token is being obtained?

Just to make sure I understand the issue:

  1. New day, user signs out then back in
  2. tokens are issued
  3. access token eventually expires, token refresh is initiated
  4. refresh fails with The refresh token is invalid or expired.

Or do these errors occur when the user tries to sign in?

@morfees
Copy link
Author

morfees commented Mar 5, 2025

Is code within your app signing users out? Are you certain a new refresh token is being obtained?

Yes, when a user opens the app for the first time in a day, the code triggers a sign-out to force them to reconnect. My assumption is that when the user logs back in, a new token is issued—can you confirm if that is correct?

Here’s the sequence of events:
1. A new day begins, and the user is signed out, then logs back in.
2. New tokens are issued.
3. The access token eventually expires (we do not auto-renew or listen for the expiration event).
4. A request fails with a 401 error, prompting us to use the refresh token to obtain a new access token. Most of the time, this works as expected.
5. However, for some users, the token renewal fails with one of two errors:
• “Load failed”: We assume this is a network issue. Previously, many users were experiencing multiple unprompted logouts throughout the day, so we implemented a workaround to mitigate this. To prevent the refresh token from being lost, we call setTokens with the existing tokens before attempting renewal. However, this may lead to the refresh token expiring since Okta might have already marked it as used, and the network error delayed the response. This seems to be the most logical explanation.
• “The refresh token is invalid or expired.”: In this case, we simply throw the error and allow Okta to handle the token renewal.
6. Since it is an offline first app, when a network request fail, it is retried each X seconds

Does this behavior align with what you’d expect? Any insights would be appreciated.

@morfees
Copy link
Author

morfees commented Mar 5, 2025

Also sometimes when we try to renew the token we get these errors too :

  • The client specified not to prompt, but the user is not logged in.
  • The tokenManager has no token for the key: accessToken

The app is mainly used as a PWA in an ipad BTW

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants