Library / SDK
auth0/auth0-spa-js avatar
auth0/auth0-spa-js

auth0-spa-js: Auth0 login for single page applications

Auth0 authentication for Single Page Applications (SPA) with PKCE

1,016 stars436 forksTypeScriptMIT

At a glance

What is it?
auth0-spa-js is Auth0's TypeScript SDK for putting authorization code with PKCE into a browser application, installable from npm or from a CDN. It handles token caching, silent renewal and DPoP, and its newest capabilities, Online Refresh Tokens and Experiment Center, are still early access features with a documented conflict between them and Ephemeral Sessions.
Who is it for?
Adopt auth0-spa-js if your browser application authenticates against an Auth0 tenant and you would rather not hand roll PKCE, silent renewal and cross-tab token coordination. Do not adopt it if you need a provider neutral OIDC client, if you need Online Refresh Tokens against a resource server with Ephemeral Sessions enabled, or if you need Experiment Center before Auth0 grants early access.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the SDK wraps for you

auth0-spa-js exists to put one specific protocol combination into a browser: the authorization code grant with PKCE, described in the package description as an Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE.

The flow is the interesting part. A browser application cannot keep a client secret, so the code grant with PKCE has the browser send a code challenge on the way to the authorization server and a code verifier on the way back, which means the callback is not replayable and the tokens never sit in a URL. Implementing that correctly in an application is a known source of subtle bugs, and the SDK is the answer to that problem rather than a general purpose identity client.

The library is written in TypeScript and ships as @auth0/auth0-spa-js. Its runtime dependencies describe what it takes off your hands: @auth0/auth0-auth-js as the core auth layer, browser-tabs-lock to coordinate token use across open tabs, dpop for DPoP bound tokens, and es-cookie for the cookie based session handling. The last two of those are the parts you would otherwise have to write yourself.

Installing from npm or pulling the CDN build

The README gives two ways in. For a bundled project, the npm route:

sh
npm install @auth0/auth0-spa-js

For a page that loads the SDK without a build step, the CDN route, with a version path that matches the release series:

html
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.28/auth0-spa-js.production.js"></script>

The versioned CDN path is worth noticing. The URL carries the 2.28 series rather than a bare latest, so a page that pins the script tag pins a specific minor line. The package metadata shows the same care about artefacts: the npm entry points are a CommonJS main at dist/lib/auth0-spa-js.cjs.js, an ES module at dist/auth0-spa-js.production.esm.js and type declarations at dist/typings/index.d.ts, and a ccu block records the CDN bucket, the local path and the main bundle file name, which is how the same build is published to cdn.auth0.com.

The build itself is rollup, driven from a Makefile with install, lint, test, build and publish targets. The build script runs rollup in production mode and then an es-check step, so a bundle that drifts outside the supported language level fails the build rather than reaching users.

The dashboard settings that have to line up

The SDK is useless until the Auth0 application is configured to match it, and the README is specific about which settings.

You create a Single Page Application in the Auth0 Dashboard, and for an existing application you open the Settings tab, scroll down to Show Advanced Settings, open the OAuth tab, and make sure the JsonWebToken Signature Algorithm is set to RS256 and that OIDC Conformant is enabled. Those two settings are what make the token and the session behave the way the SDK expects.

Next come the three URL lists under Application URIs, and for a local app the README gives all three as http://localhost:3000: Allowed Callback URLs, Allowed Logout URLs and Allowed Web Origins. The callback list is the one with a subtlety, since it may include a path depending on where you handle the callback. These values should reflect the origins your application actually runs on, which means the local values are placeholders rather than a template to keep.

Finally, take the Client ID and Domain from the Basic Information section. Those two values, plus a callback URL, are the entire configuration the SDK needs.

One client instance and the silent token check

The SDK expects a single client instance, created before your application renders or initialises. The README shows two ways to create it, one async factory and one direct constructor:

js
import { createAuth0Client } from '@auth0/auth0-spa-js';

//with async/await
const auth0 = await createAuth0Client({
  domain: '<AUTH0_DOMAIN>',
  clientId: '<AUTH0_CLIENT_ID>',
  authorizationParams: {
    redirect_uri: '<MY_CALLBACK_URL>'
  }
});

The second form instantiates Auth0Client on its own, and the README attaches a warning to it: if you do this, you need to check the session yourself. That check is a call to getTokenSilently wrapped in a try block, where the only error you swallow is login_required:

js
try {
  await auth0.getTokenSilently();
} catch (error) {
  if (error.error !== 'login_required') {
    throw error;
  }
}

That pattern is worth copying exactly rather than approximating. The distinction between a user who is simply not logged in and a genuine failure is the difference between showing a login prompt and showing an error page, and the loose catch that swallows everything is a common way to hide real problems.

The createAuth0Client form does this for you, which is why the README recommends it for new projects.

Redirect login and the callback route

Login is a redirect to the Universal Login Page, and the return trip is a separate piece of code in your callback route. The button is plain markup:

html
<button id="login">Click to Login</button>

and the handler calls loginWithRedirect, which sends the user away:

js
//redirect to the Universal Login Page
document.getElementById('login').addEventListener('click', async () => {
  await auth0.loginWithRedirect();
});

//in your callback route (<MY_CALLBACK_URL>)
window.addEventListener('load', async () => {
  const redirectResult = await auth0.handleRedirectCallback();
  //logged in. you can get the user profile like this:
  const user = await auth0.getUser();
  console.log(user);
});

The load listener pattern matters because the callback is a full page load rather than a client side route change, so the SDK has to complete the code exchange before your app can read a user. handleRedirectCallback does the exchange and returns a redirect result, and getUser then returns the profile.

For anything beyond this basic path, the repository carries a long list of scenario documents under examples/, including refresh-tokens, online-access, dpop, mfa, passkeys, organizations, token-vault, myaccount-api, step-up-authentication, enterprise-connect, custom-token-exchange, native-to-web-sso, anonymous-sessions, data-caching, session-expiry-ipsie, logging-out, calling-an-api and sdk-configuration. Those files, plus EXAMPLES.md and FAQ.md, are where the detail lives.

Online Refresh Tokens and the Ephemeral Sessions trap

The newest token feature is Online Access, and the README marks it as early access with a note to contact an Auth0 representative for access. The mechanics are specific.

To use Online Refresh Tokens you set refreshTokenMode to RefreshTokenMode.Online, together with useRefreshTokens: true and useDpop: true:

js
import { createAuth0Client, RefreshTokenMode } from '@auth0/auth0-spa-js';

const auth0 = await createAuth0Client({
  domain: '<AUTH0_DOMAIN>',
  clientId: '<AUTH0_CLIENT_ID>',
  useRefreshTokens: true,
  refreshTokenMode: RefreshTokenMode.Online,
  useDpop: true,
  authorizationParams: {
    redirect_uri: '<MY_CALLBACK_URL>'
  }
});

The README describes the result: non-rotating refresh tokens bound to the Auth0 session lifetime, with the SDK injecting the online_access scope and routing renewal through the refresh-token grant. The default mode is RefreshTokenMode.Offline, which is the rotating behaviour, so online mode is opt-in in two ways at once.

The warning is the part to read twice. Online Refresh Tokens do not support resource servers with Ephemeral Sessions enabled. Enabling allow_online_access and Allow for Ephemeral Sessions on the same resource server makes the authorization server issue an Online Refresh Token that is then rejected with invalid_grant on the very next refresh. The fix given is to disable Allow for Ephemeral Sessions on any resource server used with refreshTokenMode: RefreshTokenMode.Online, which is exactly the kind of interaction that a feature in early access is likely to have.

Experiment Center needs a variant id on every request

The second early access feature is Experiment Center, which the README describes as letting you A and B test your login flow, with Auth0 assigning each user to a variant server-side.

The SDK's part is small: when you need to force a specific variant, for testing or to apply a decision from a feature flag tool, you pass experiment_id and variation_id on the login call.

js
await auth0.loginWithRedirect({
  authorizationParams: {
    experiment_id: '<EXPERIMENT_ID>',
    variation_id: '<VARIATION_ID>'
  }
});

The scope of the override is what makes this easy to get wrong. It applies to that request only, and the next login without those parameters reverts to the server side assignment. So a forced variant is a debugging and flagging tool, not a way to pin an experiment. If you were expecting the override to stick for a user, it will not, and the only symptom is that the variant seems to change between logins.

This feature is also gated behind early access, so the same note applies as with Online Access: you need to contact an Auth0 representative to request access.

Release pace, tooling and the licence

The project ships often. Recent releases are v2.28.2 on 2026-10-02, v2.28.3 on 2026-10-09 and v2.28.4 on 2026-10-14, and the last push to the repository was on 2026-10-19. The package version in package.json is 2.28.4, matching the newest tag.

That cadence has a cost as well as a benefit. A patch release every few days means you will be tempted to track the newest tag, and in a browser SDK that choice is a supply chain decision rather than a formality. Pinning a version in your lockfile and updating on your own schedule is the safer habit.

The repository is well instrumented for that risk. It carries a CHANGELOG.md, a MIGRATION_GUIDE.md, a.semgrepignore, an.eslintrc.security file with a dedicated lint:security script that runs eslint over the TypeScript sources with its own config, a.npmrc, a.shiprc file, a.version file and a.npmignore. Tests run on jest with coverage, and integration tests run through cypress with a cypress.config.js. The build additionally runs an es-check step, which guards the language level of the published bundle. The licence is MIT, stated in the LICENSE file and in the package metadata.

Editorial conclusion

Adopt auth0-spa-js if your browser application authenticates against an Auth0 tenant and you would rather not hand roll PKCE, silent renewal and cross-tab token coordination. Do not adopt it if you need a provider neutral OIDC client, if you need Online Refresh Tokens against a resource server with Ephemeral Sessions enabled, or if you need Experiment Center before Auth0 grants early access. Verify first that your Auth0 application uses RS256 with OIDC Conformant enabled, that your callback, logout and web origin URLs are all listed, and that you read the Online Refresh Tokens section of the documentation before enabling refreshTokenMode: RefreshTokenMode.Online on any resource server.

Frequently asked questions

How do I install the auth0-spa-js SDK?

Run npm install @auth0/auth0-spa-js in your project, or load the CDN build with a script tag pointing at https://cdn.auth0.com/js/auth0-spa-js/2.28/auth0-spa-js.production.js. The versioned CDN path pins the 2.28 series.

What Auth0 application settings does auth0-spa-js require?

Create a Single Page Application in the Auth0 Dashboard, set the JsonWebToken Signature Algorithm to RS256 and enable OIDC Conformant under Advanced Settings and the OAuth tab, then add the Allowed Callback URLs, Allowed Logout URLs and Allowed Web Origins. The README uses http://localhost:3000 for all three in the local case.

What is the difference between RefreshTokenMode.Offline and RefreshTokenMode.Online?

refreshTokenMode defaults to RefreshTokenMode.Offline, which uses rotating refresh tokens. Online mode gives non-rotating refresh tokens bound to the Auth0 session lifetime, with the SDK injecting the online_access scope, and it requires both useRefreshTokens: true and useDpop: true. Online Access is an early access feature.

Why does my Online Refresh Token fail with invalid_grant?

The README warns that Online Refresh Tokens do not support resource servers with Ephemeral Sessions enabled. Enabling allow_online_access and Allow for Ephemeral Sessions together makes the issued Online Refresh Token fail on the next refresh, so the guidance is to disable Allow for Ephemeral Sessions on any resource server used with refreshTokenMode: RefreshTokenMode.Online.

Official sources

  1. auth0/auth0-spa-js on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/auth0-auth0-spa-js.svg)](https://hysenlabs.com/projects/auth0-auth0-spa-js)

Was this useful?