Ask
28

Single-page app cannot renew its token — the renewal call comes back saying there is no refresh token

My single-page app signs in fine and gets an access token. When that token expires, the silent renewal fails with an error saying a refresh token is missing.

The user then gets bounced to the login page, which they experience as being logged out every hour for no reason.

I upgraded the client library recently and I think the behaviour changed, because this used to work through a hidden frame without any refresh token being involved.

What changed, and what is the correct configuration now?

4 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @spa_sami · yesterday

    Strongly second the backend-for-frontend suggestion. I moved a single-page app to it after the third round of browser privacy changes broke silent renewal again, and the thing that convinced me was that it stopped being a moving target.

    Token handling in the browser has been rewritten roughly every eighteen months for years: implicit flow, then authorisation code with a challenge, then silent renewal in an iframe, then refresh tokens with rotation. Each migration was work. A session cookie against your own backend has not changed at all in that time because it never depended on third-party cookies in the first place.

    The cost is a server-side component you now run, and it does not suit a purely static deployment. If you already have a backend, it is much less work than it sounds — it is a proxy plus a session store.

    27
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @token_shape_theo · 3d ago

    Practical detail while you debug: check what the token actually contains after sign-in, rather than what you asked for.

    Decode the access token and look at its scopes. If the offline access scope is not in there, your request for it was not granted — and the reason is usually the application registration rather than your code, so you can stop rereading the front end.

    That one check distinguishes "I did not ask correctly" from "I asked and was refused", which are different fixes in different places.

    21
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @silent_renew_saskia · 2d ago

    Your memory is right and the change is not really the library's doing — browsers broke the old mechanism and the libraries followed.

    What used to happen. Silent renewal worked through a hidden iframe: the app loaded the authorisation endpoint in an invisible frame, the browser sent the session cookie for the identity provider, the provider recognised the still-valid session and returned a fresh token without any user interaction. No refresh token anywhere.

    That depends entirely on the identity provider's cookie being sent in a third-party context — your app is on one origin, the provider on another. Browsers now block third-party cookies by default, with varying degrees of finality. So the iframe loads, no cookie is sent, the provider sees an anonymous visitor, and silent renewal fails.

    It does not fail cleanly, either — it typically fails as "login required", which reads like the session expired when the session is fine.

    The replacement is refresh tokens, with rotation, which is why your library now expects one and reports its absence.

    The configuration you need — all of these, and missing any one produces exactly your error:

    1. Enable refresh tokens in the client library. There is a flag for it. Off by default in some versions, which is the most common single cause.
    2. Request the offline access scope. This is the standard scope that asks for a refresh token. Without it the authorisation server issues an access token and nothing else, so there is genuinely no refresh token to use — which is what your error literally means.
    3. Allow the refresh token grant on the application registration. A server-side setting, separate from anything in your code. An application configured as a single-page app may have it disabled by default.
    4. Enable rotation. For a public client — which a single-page app is, because it cannot keep a secret — a non-rotating refresh token sitting in the browser is a long-lived credential. Rotation issues a new one on each use and invalidates the old, so a stolen token is usable once and detectably.

    After changing scopes, existing sessions will not have a refresh token. They were issued without one and nothing retroactively adds it. So during the rollout you will still see the error for already-signed-in users until they sign in again. Handle that path gracefully rather than treating it as a bug — catch the failure, send them through login once, and it settles.

    On where the refresh token is stored, since this is the trade you are accepting: in memory it is safest but lost on refresh of the page, which defeats the purpose. In browser storage it survives reloads and is readable by any script that runs on your origin, so a cross-site scripting flaw is a full account compromise. That is the honest cost.

    The alternative worth considering if this matters: put a thin backend in front, keep the tokens server-side, and give the browser an ordinary secure, http-only, same-site session cookie. The front end then holds no tokens at all, the cookie is not readable by script, and none of this third-party cookie machinery applies. More infrastructure, considerably fewer sharp edges — and it is the direction most of this has moved.

    30
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @csrf_carla · yesterday

    Whatever you land on, make the expiry path visible in your monitoring.

    Silent renewal failing is the kind of thing that produces a slow trickle of support messages about being logged out, with no error rate to alert on, because from the server's point of view everything is behaving correctly. Count renewal attempts and renewal failures as separate metrics and you will notice the next browser change in days rather than in months of vague complaints.

    14
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report