npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@userspace-auth/userspace-js

v1.12.9

Published

Readme

Userspace JS SDK (@userspace-auth/userspace-js)

This SDK gives auth elements and session tokens to any web page. No framework is necessary.

The sign-in occurs on the hosted pages of your Userspace instance. This package puts auth UI into a page and gives you a session token for calls to your backend.

If you use React, use @userspace-auth/react. That package gives the same token contract, and adds hooks and guards.

Install

Embed the SDK directly, and use the script tag to configure it:

<script
  src="https://cdn.jsdelivr.net/npm/@userspace-auth/userspace-js/dist/userspace.browser.js"
  data-base-url="https://acme.userspace.tech"
></script>

Or install the package and configure it in your code:

npm i @userspace-auth/userspace-js
import { configureUserspace } from '@userspace-auth/userspace-js'

await configureUserspace({
  baseUrl: 'https://acme.userspace.tech',
})

By default, the SDK also keeps the session token of the signed-in user in a __Host-session cookie on your host. The SDK refreshes the cookie before the session token expires. Thus your backend can read the session token, and your frontend sends nothing. To use only getToken() and bearer headers, set cookie: false (or data-cookie="false"). Then the SDK writes nothing to document.cookie and runs no refresh timer.

The configuration step registers the custom elements. Thus nothing renders until the configuration step is complete. The two install methods also make the API available on window.Userspace, for a page that cannot use modules.

The script tag reads data-base-url, data-auto-login and data-cookie. For the boolean attributes, true, on and 1 are on. data-cookie is the only attribute that is on when it is absent, and data-cookie="false" sets it to off. The SDK reads these attributes only from a classic script tag. A type="module" script has no document.currentScript, and thus it must call configureUserspace.

The package includes type declarations (userspace.d.ts). Thus an editor shows the full API after a plain npm install. This includes the strike-through on the deprecated fetchToken.

Developing on localhost

An instance in the Development environment trusts loopback origins on all ports (localhost, *.localhost, 127.0.0.1, [::1]). The instance trusts these origins together with the app URL and the trusted origins that you configured. Thus a page on http://localhost:5173 can call a hosted instance with only baseUrl. An instance in a different environment trusts only its app URL and its trusted origins. Use a Production instance for production, because its origin checks are the strictest that the platform has.

Running on another site than baseUrl

Your page can be on a different site than baseUrl. Examples are localhost with a hosted instance, or an application on its own domain. Then the browser does not send the sign-in cookie to the auth host, and the SDK signs in with an authorization code and PKCE. You set no option, because the SDK compares the two sites.

On the first page load of a tab, the SDK makes one full-page round trip to the auth host and back. Then the SDK removes the code from the address bar. Each tab keeps its own refresh token in memory, and also in sessionStorage between two pages of the tab. Thus a reload or a link in your application stays signed in without that round trip. A new tab signs in independently.

getToken(), the __Host-session cookie, signOut(), the settings popup and the elements work the same as on the same site.

A tab can open while a different tab is signed in. Such a tab reads the session cookie of the signed-in tab for getToken(). The tab makes the round trip when it first calls the API. That first call occurs on load with autoLogin, or when an element or the settings popup needs it.

At this time, this works only for Development instances.

Calling your backend

Your own backend needs no token at all

By default, the SDK keeps the session token of the signed-in user in a __Host-session cookie on your host. The SDK refreshes the cookie before the session token expires. Your backend reads the session token from the cookie. Thus a page load, a form post or a plain fetch to your origin is authenticated:

// No Authorization header: the browser sends the cookie on its own.
const res = await fetch('/api/me')

The cookie transport is off by default in your backend. Set it to on. For Go:

u, err := userspace.New(userspace.Options{CookieTransport: true})

For a JavaScript backend, refer to Verifying sessions in a JavaScript backend.

The cookie transport has two effects. First, your backend refuses a request that changes state (POST, PUT, PATCH, DELETE) when the request sends the cookie from a different origin. This prevents an action by a different site in the name of your user. Second, your backend gets a third answer together with signed in and signed out.

The third answer: "unknown". The session token in the cookie has a life of minutes. A browser that was idle can send a request with no session token while the user is signed in. This occurs because the SDK did not refresh the session token yet. Your backend reports this state as unknown, not as signed out.

This is important for pages that you render on the server. After a pause longer than the life of the session token, the first request renders unknown. The page becomes correct a moment later, when this SDK refreshes the session token on the client. Thus render unknown with your application shell, not with your sign-in page. If you render the sign-in page, a signed-in user sees it for a moment. A server-side refresh that fully removes this flicker is not available yet.

unknown never gives permission for an action. For each authorization decision, use unknown the same as signed out. The backend SDK does this.

Verifying sessions in a JavaScript backend

The package has a server entry point, @userspace-auth/userspace-js/server. The server entry point verifies the session token locally in your backend, with the published keys of your instance. It uses no secret and cannot issue a token. It uses only the Fetch API and WebCrypto, and thus it runs on Node, Deno, Bun and edge runtimes.

import { createVerifier } from '@userspace-auth/userspace-js/server'

// baseUrl is the same value the browser SDK uses; USERSPACE_BASE_URL works too.
const userspace = createVerifier({ baseUrl: 'https://acme.userspace.tech', cookieTransport: true })

authenticate(request) accepts a standard Request. It never throws because of the contents of the request. It returns one of two answers:

  • { ok: true, session }, where the session has userId, sessionId, expiresAt and transport.
  • A refusal with a status, a fixed reason and a state.

unauthorized(refusal) changes a refusal into a Response:

  • 401 with the bearer challenge.
  • 403 for a cookie that came from a different origin.
  • 503 when the verifier did not fetch the keys of your instance yet.

In SvelteKit, use src/hooks.server.js:

import { createVerifier } from '@userspace-auth/userspace-js/server'
import { env } from '$env/dynamic/private'

const userspace = createVerifier({ baseUrl: env.USERSPACE_BASE_URL, cookieTransport: true })

export async function handle({ event, resolve }) {
  const auth = await userspace.authenticate(event.request)
  if (auth.preflight) return resolve(event)

  // A 403 or a 503 is never a page to render.
  if (!auth.ok && auth.status !== 401) return userspace.unauthorized(auth)

  event.locals.session = auth.ok ? auth.session : null
  // 'signed-in', 'signed-out' or 'unknown': render 'unknown' with your application shell,
  // not your sign-in page, and let the browser SDK recover.
  event.locals.sessionState = auth.state

  if (event.url.pathname.startsWith('/api/') && !auth.ok) return userspace.unauthorized(auth)
  return resolve(event)
}

Your GET and HEAD handlers must not change state. The origin check applies only to POST, PUT, PATCH and DELETE, because a browser sends the cookie with a cross-site navigation. Nothing can do the check of your handlers for you.

The default clock skew is 10 seconds. You can increase it to a maximum of 60 seconds with clockSkew. trustedOrigins is a list of more origins that can make requests that change state with the cookie. Use it for a frontend on a different origin than this backend. The list is empty by default. keyStatus() reports if the verifier fetched the keys, for a readiness probe.

Running on bearer headers alone

Set cookie: false, or data-cookie="false" on the script tag. Then the SDK writes no cookie, runs no refresh timer, and gets a session token when a call asks for one. Use this mode if you only call an API on a different origin, or if you do not want a cookie on your host.

The SDK also uses this mode automatically in one condition. __Host- cookies must have Secure. If the browser does not store such a cookie on your page, the SDK continues in the bearer-only mode. The SDK writes one message about this to the console. This is important for development on plain http://localhost, where browsers do not agree if a page can set a Secure cookie.

Sending the token yourself

getToken() returns a session token that your backend can verify locally, or null when no user is signed in. Use it for an API on a different origin, where the browser sends no cookie of yours. Send the session token as a bearer token:

import { getToken } from '@userspace-auth/userspace-js'

async function loadProfile() {
  const token = await getToken()
  if (!token) return // signed out

  const res = await fetch('https://api.example.com/me', {
    headers: { Authorization: `Bearer ${token}` },
  })
  return res.json()
}

The signed-out state is a value, not an error. getToken() rejects only when something failed, for example the network or a broken response. Thus a catch here shows a real problem, not an anonymous visitor. The SDK refreshes session tokens automatically. Each call returns a cached session token until it expires, and after that the call fetches a new one.

There is one limit. signOut() removes the cached session tokens. But a sign-out in a different location (a different tab, the hosted pages) does not remove them. Thus getToken() continues to return a session token from before that sign-out until the session token expires.

Send this session token only to your backend. A session token gives access to no API of the platform. The Frontend API, which is the auth server of your instance, accepts only its own token with a separate signature. But each backend with the same base URL accepts the session token until it expires.

On the backend, verify the session token with the Userspace SDK for your stack. For Go, that SDK is github.com/userspace-auth/sdk-go. Any JOSE library also works. The session token is a standard RS256 JWT, and your instance publishes the keys that sign it at https://<your-instance>/.well-known/jwks.json.

fetchToken() is deprecated

fetchToken() works at this time, and version 2.0.0 removes it. It returns the raw { token, ... } response and throws on a 401. Thus the signed-out state comes as an error, not as a value. The React SDK always returned null, and the two SDKs did not agree. Code for one SDK handled the signed-out state of the other SDK incorrectly.

// before
try {
  const { token } = await fetchToken()
} catch (err) {
  /* signed out, or a real failure, indistinguishable without inspecting err */
}

// after
const token = await getToken() // null when signed out

Elements

The configuration step registers fifteen custom elements. These include <x-signed-in> and <x-signed-out>, which show content by auth state. They also include <x-signin-button>, <x-signout-button> and <x-profile-settings>. Each element accepts the theme and primary attributes.

<x-signed-in>
  <p>You are signed in.</p>
  <x-signout-button></x-signout-button>
</x-signed-in>

<x-signed-out>
  <x-signin-button primary="emerald"></x-signin-button>
</x-signed-out>

<x-signin-button> returns the visitor to the page where the visitor clicked the button. To send the visitor to a different address, give the button a redirect-uri. This is useful for a marketing page that sends the visitor to your application:

<x-signin-button redirect-uri="https://app.example.com/"></x-signin-button>

The target must be an absolute URL, and its origin must be on the allowlist of the instance. The server validates the target. If the server does not trust the target, the server ignores it and uses the app URL.

<x-authorized> and <x-non-authorized> are older names of <x-signed-in> and <x-signed-out>. The SDK supports the old names and the new names, and it prefers none of them.

Triggering Userspace from your own markup

<x-trigger> wraps your element and renders nothing itself. When the user clicks your element, <x-trigger> starts a Userspace action (a tap is also a click). The actions are:

  • settings, which opens the settings popup.
  • sign-in, which goes to the hosted sign-in page.
  • sign-out, which signs the user out.

Wrap a real interactive element, for example a button or a menu item of your design system. Then keyboard activation works with no more code. Put the trigger in <x-signed-in>. Without a session, settings logs a console warning and opens nothing.

sign-in and sign-out accept the same redirect-uri attribute as <x-signin-button>. The attribute gives the address where the user goes after the action.

<x-signed-out>
  <x-trigger action="sign-in" redirect-uri="https://app.example.com/">
    <button class="your-cta">Get started</button>
  </x-trigger>
</x-signed-out>

<x-signed-in>
  <x-trigger action="settings">
    <button class="your-menu-item">Account settings</button>
  </x-trigger>
  <x-trigger action="sign-out">
    <button class="your-menu-item">Sign out</button>
  </x-trigger>
</x-signed-in>

Each action is also available as a function. All settings triggers and callers share one popup instance.

await Userspace.openSettings()
await Userspace.signOut()

signIn() goes to the hosted sign-in page. It sends the current URL as redirect_uri, and thus the user returns to the same page. This is the same navigation that the autoLogin option does, but on a click and not on each signed-out visit. To go to a different address, give redirectUri. The same allowlist rule as for signOut applies:

Userspace.signIn()
Userspace.signIn({ redirectUri: 'https://app.example.com/' })

signOut() stops each session in this browser, not only the current session. The server stops the full cookie-scoped session, and thus other tabs also sign out. signOut() sends a POST to the sign-out endpoint and then goes to the hosted sign-in page. If you give redirectUri, it goes to that address. redirectUri must be an absolute URL, and its origin must be on the allowlist of the instance. The server validates the target, and a target that the server does not trust becomes the sign-in page:

await Userspace.signOut({ redirectUri: 'https://app.example.com/logged-out' })

registerElements() registers the custom elements and does not configure the SDK. It is for hosts that configure the SDK with a different method. configureUserspace calls it, and most pages do not need to call it directly.

License

MIT.