Silent Authentication Systematically Requiring Consent in SPA - Breaking Token Rotation for oidc-spa Library Users

I’m the maintainer of oidc-spa, a provider-agnostic OIDC client library for single page applications ( Auth0 | OIDC SPA ). I’m reporting a critical issue affecting all Auth0 users of our library where silent authentication has suddenly started systematically requiring consent.

The Problem:
Silent authentication requests previously worked correctly now systematically return consent_required errors, making background token renewal impossible. This breaks the SPA user experience by forcing frequent re-authentication.

Impact Scale:
This isn’t just affecting one application - it’s impacting all users of the oidc-spa library who have chosen Auth0 as their provider. Our library is designed to work with any OIDC-compliant provider using standard flows, and Auth0 was previously working seamlessly.

Configuration Details:

  • SPA applications using Authorization Code Flow with PKCE
  • Custom domain setup (first-party context)
  • Refresh Token Rotation enabled
  • No offline_access scope (intentionally avoiding for security reasons)
  • Provider-agnostic implementation following OIDC standards

What Changed:
The applications were working correctly, then suddenly stopped without any configuration changes on our end. The systematic nature suggests a platform-level change rather than individual configuration issues.

Security Concerns:
The suggested workaround of using offline_access scope is not acceptable for security reasons, as it creates non-expiring refresh tokens in the browser. This fails basic security audits and contradicts security best practices for SPAs.

Broader Impact:
This issue affects the viability of provider-agnostic OIDC implementations with Auth0 and forces users to choose between:

  1. Poor security (long-lived tokens)
  2. Poor UX (frequent re-authentication)
  3. Major architectural changes (implementing BFF pattern)

Has there been a platform change that affected silent authentication behavior? This is impacting multiple applications using standard OIDC flows that should be supported. Any guidance on maintaining both security and usability would be greatly appreciated.

Hi @garronej

Welcome to the Auth0 Community!

According to Auth0 documentation, the consent_required error during silent authentication (prompt=none) occurs when the user was logged in at Auth0, but needs to give consent to authorize the application. This can happen when the application is requesting scopes or permissions that require explicit user consent, and silent authentication cannot proceed without user interaction.

The systematic consent_required errors during silent authentication are most likely stemming from the limitations of iframe-based session checks in modern browsers, compounded by how Auth0 specifically issues refresh tokens. To utilize your enabled Refresh Token Rotation (RTR) and resolve this without compromising security, you must request the offline_access scope. Fortunately, Auth0’s implementation of RTR for SPAs allows you to enforce strict expiration limits on these tokens, directly addressing your valid security concerns regarding non-expiring credentials in the browser.

There are two intersecting concepts at play here regarding OIDC standards, Auth0’s architecture, and browser security:

  1. The Brittle Nature of Silent Auth (prompt=none): Even with a Custom Domain (first-party context), modern browsers are increasingly aggressive with cookie partitioning, Intelligent Tracking Prevention (ITP), and strict cross-origin policies. If the browser blocks or drops the Auth0 session cookie within the hidden iframe used for silent authentication, Auth0 cannot verify the session and will default to requiring user interaction, often surfacing as a consent_required or login_required error.

  2. The offline_access Misconception in Auth0: You mentioned that Refresh Token Rotation is enabled, but the offline_access scope is intentionally omitted for security. In Auth0’s OIDC implementation, a refresh token will not be issued unless the offline_access scope is explicitly requested in the authorization flow.

    • Without offline_access, your SPA is not actually receiving a refresh token to rotate. Instead, it is falling back entirely on the iframe-based silent authentication against the Auth0 web session.

    • Your security concern is highly valid: historically, offline_access meant a permanent, non-expiring token. However, when used in conjunction with a SPA application type and Refresh Token Rotation in Auth0, these refresh tokens are not infinite. They are single-use (rotating) and bound by strict, configurable expiration timers.

[Solution]

To restore a seamless background token renewal experience while satisfying stringent security audits, you should bridge the gap between RTR and the offline_access scope using strict configurations.

  1. Request the offline_access Scope
    Update the oidc-spa library configuration for Auth0 to include offline_access in the scope array. This is the required trigger for Auth0 to dispense a rotating refresh token to the SPA.

  2. Enforce Strict Refresh Token Lifetimes
    To satisfy security audits and ensure tokens are not long-lived, you can (and should) configure aggressive expiration policies. Instruct your Auth0 users to configure their SPA settings in the Auth0 Dashboard:

    • Navigate to Applications > Select the SPA > Settings tab.

    • Scroll down to Refresh Token Expiration.

    • Set Absolute Expiration (e.g., 24 hours). The token cannot be used beyond this time, regardless of rotation.

    • Set Inactivity Expiration (e.g., 30 minutes). If the user closes the app and the token isn’t rotated within this window, it dies.

  3. Verify API Consent Settings
    Just to be absolutely certain that backend configurations aren’t forcing the consent screen: ensure that the API (Audience) your SPA is requesting access to has the “Allow Skipping User Consent” toggle enabled (Dashboard > Applications > APIs > Settings). Since you are on a custom domain, first-party applications should inherently skip consent, but this setting guarantees it.

In summary:

  1. Understand that Auth0’s silent authentication using prompt=none requires that the user already has an active Auth0 session and that no new consent is required, because prompt=none explicitly forbids user interaction.
  2. Recognize that Refresh Token Rotation in Auth0 is documented as requiring the offline_access scope to function, because Auth0 documentation states: “You must enable offline access and request the offline access scope in the client SDK.”
  3. Note that without the offline_access scope, Auth0 may not issue a refresh token, and subsequent silent authentication attempts may fail with consent_required if the authorization server interprets the request as requiring new consent.
  4. Review the Auth0 documentation on configuring silent authentication, which explicitly lists consent_required as a possible error when “the user was logged in at Auth0, but needs to give consent to authorize the application.”
  5. Check your tenant-level Log In Session Management settings, specifically the Require log in after timeout setting, because Auth0 support guidance indicates that session expiration can trigger consent requirements during silent authentication attempts.
  6. Consider that a platform change to Auth0’s session or consent handling may have occurred, but the documented behavior requires either the offline_access scope for refresh token rotation or acceptance that prompt=none will fail when consent is needed.
  7. Evaluate the Backend-for-Frontend (BFF) pattern as the recommended security-first approach for SPAs, because it avoids storing tokens in the browser and eliminates the need to choose between security and silent authentication reliability.

Kind Regards,
Nik

Hello @nik.baleca,

Thank you very much for taking the time to respond and for pointing me to the relevant documentation.

I think I now understand what Auth0 is doing, but I’d still like to understand why it behaves this way.

I think we’re discussing two different issues. Browser restrictions around third-party cookies, iframe-based authentication, and Refresh Token Rotation are all important topics, but they aren’t related to the behavior I was reporting.

The behavior I was describing is the following:

  • The SPA requests an authorization code for a first-party API.
  • “Allow Skipping User Consent” is enabled.
  • There is an active Auth0 session (the session cookies are present and accepted by Auth0).

Yet Auth0 still displays the following confirmation dialog on every authorization request:

Hi Joseph Garrone,

oidc-spa-example is requesting access to your dev-r2h8076n6dns3d4y account.

[Decline] [Accept]

The key piece of documentation I had missed is this:

Even when consent is skipped for first-party applications, a login confirmation prompt may still appear when the application uses a non-verifiable callback URI (such as localhost or a custom URI scheme). This protects users against application impersonation on the same device.

After deploying exactly the same application to a production environment (using a same-site custom Auth0 domain), everything behaves exactly as expected:

  • No consent dialog is shown.
  • Silent authentication works correctly.
  • Session restoration works correctly.

So this isn’t a production issue after all.

However, I still think this behavior creates a significant amount of friction during development.

Concretely, every time a developer reloads the application—which happens constantly while writing code, they have to approve the exact same confirmation dialog again.

It also makes the development experience fundamentally different from production. I now have to explain to every oidc-spa user:

“Don’t worry, on localhost you’ll have to approve this dialog over and over again, and silent session restoration won’t work. Once you deploy your application, everything will behave differently.”

This is surprisingly difficult to explain to developers who are learning OIDC because the application behaves correctly only after deployment.

More generally, I find the confirmation dialog itself difficult to justify from a user experience perspective.

Technically, I understand what is happening. The SPA is the OAuth client, while the backend is a resource server. The authorization server is therefore asking the user whether they authorize the client to obtain an access token for that API.

But for a first-party application, this distinction is purely an implementation detail.

The user isn’t authorizing a third-party service. They’re simply using one application provided by one organization. Whether the OIDC client runs in the browser or on a backend, whether the architecture consists of one service or ten microservices, is completely irrelevant to them.

From the user’s perspective, the dialog effectively says:

“You just signed in to this application. Do you allow this application to access your account?”

The only sensible answer is “Yes.” Declining simply prevents the application they intentionally opened from functioning.

As a result, the dialog doesn’t help users make an informed security decision. Instead, it teaches them to mechanically click “Accept” every time they see it. That’s generally considered poor UX for security prompts.

So I have two genuine questions:

  • Why is explicit user confirmation required at all when a first-party SPA requests an access token for a first-party API?
  • Is there any way to disable this confirmation dialog on localhost during development? It would significantly improve the developer experience while still allowing developers to exercise the exact same authorization flow they will use in production.

Thank you again for your help.

Hi again!

I am sorry for the delayed response. In order to answer your questions:

The root cause of this could be your web browser. Your browser has likely enabled strict Third-Party Cookie Blocking .

This browser option breaks the hidden iframe mechanism Auth0 uses to silently restore your session on localhost . Because the silent session restoration fails, your app forces a full, interactive login on every reload. The consent screen is appearing because Auth0 has a hardcoded security rule: applications running on localhost are never allowed to silently skip user consent.

[OFFICIAL WORKAROUND]
In order to skip the localhost consent screen on your development environments:

OPTION 1:

  • In Chrome: Navigate to chrome://settings/trackingProtection (or chrome://settings/cookies ) and either disable tracking protection temporarily or add [*.]auth0.com to the list of sites allowed to use third-party cookies.

OPTION 2:

  • Since third-party cookies are going away permanently across all browsers, the modern best practice is to stop relying on the hidden iframe entirely by using Refresh Token Rotation.
  1. In your Auth0 Dashboard, go to your Application settings and enable Refresh Token Rotation .
  2. In your SPA codebase (if using @auth0/auth0-react or @auth0/auth0-spa-js ), update your Auth0Provider/Client configuration to include:
useRefreshTokens: true,
cacheLocation: 'localstorage'

This allows the SDK to save a rotating refresh token in your browser’s local storage, surviving page reloads without ever needing to rely on third-party cookies.

  • If you implement Refresh Tokens but still want to eliminate the consent screen on your very first local login, you must stop using localhost .

Kind Regards,
Nik

Hello @nik.baleca,

I’d like to clarify that this is product feedback from the maintainer of an OpenID Connect client, rather than a support question about cookies, iframes, or refresh-token rotation.

The unresolved issue is:

Auth0 provides no way for a first-party SPA running on localhost to silently obtain tokens for its first-party API without repeatedly displaying a confirmation prompt.

The suggested workaround, persisting refresh tokens in localStorage, reduces the frequency of the prompt, but weakens the application’s security posture. Tokens stored there are accessible to any JavaScript executing in the application, increasing their exposure to both XSS vulnerabilities and compromised third-party dependencies or supply-chain attacks. This is not an acceptable solution for applications whose security requirements call for in-memory token storage.

This creates an unfortunate incentive: developers must either accept a development experience that differs significantly from production or persist credentials less securely.

Could you please escalate this product gap to the Auth0 engineering team? Ideally, Auth0 would provide a secure way for explicitly configured first-party SPAs and APIs to skip this prompt on localhost.

If it is escalated, a tracking reference or confirmation would be greatly appreciated. Since the currently marked solution explains the behavior but does not address this concern, I also suggest unmarking it as the solution.

Best,
Joseph