@userspace-auth/userspace-js
v1.10.8
Published
Readme
Userspace JS SDK (@userspace-auth/userspace-js)
Auth elements and session tokens for any web page, with no framework required.
Sign-in itself happens on your Userspace deployment's hosted pages. This package drops auth UI into a page and hands you a token to call your own backend with.
Using React? Reach for @userspace-auth/react instead. It publishes the same token
contract with hooks and guards on top.
Install
Embed it directly, and configure it from the script tag:
<script
type="module"
src="https://unpkg.com/@userspace-auth/userspace-js"
data-base-url="https://acme.userspace.tech"
></script>Or install and configure it yourself:
npm i @userspace-auth/userspace-jsimport { configureUserspace } from '@userspace-auth/userspace-js'
await configureUserspace({
baseUrl: 'https://acme.userspace.tech',
})Configuring is what registers the custom elements, so nothing renders until it has run.
Both forms also expose the API on window.Userspace, for a page that cannot use modules.
The script tag reads data-base-url and data-auto-login. The boolean attribute
counts true, on, and 1 as on.
Type declarations ship with the package (userspace.d.ts), so an editor sees the full
surface from a plain npm install, including the strike-through on the deprecated
fetchToken.
Calling your backend
getToken() returns a JWT your backend can verify locally, or null when nobody is
signed in. Send it 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()
}Being signed out is a value, not an error. getToken() rejects only when something
actually failed, such as the network or a broken response, so a catch here means a real
problem rather than an anonymous visitor. Tokens refresh themselves: each call returns a
cached token until it expires and fetches a new one after that.
One gap to know about: nothing evicts a cached token when a user signs out, so a token issued before sign-out keeps resolving here until it expires.
Never send this token anywhere but your own backend. It also authenticates against the Userspace API as that user.
On the backend, verify it with the Userspace SDK for your stack, for Go that is
github.com/userspace-auth/sdk-go. Any JOSE library works too: the token is a standard
RS256 JWT signed by keys published at https://<your-deployment>/.well-known/jwks.json.
fetchToken() is deprecated
fetchToken() still works and is removed in 2.0.0. It returns the raw
{ token, ... } response and throws on a 401, so being signed out arrives as an error
rather than as a value. That disagreed with the React SDK, which has always returned
null, and code written against one mishandled the other in exactly that case.
// 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 outElements
Configuring registers fourteen custom elements, including <x-signed-in> and
<x-signed-out> to show content by auth state, <x-signin-button> and
<x-signout-button>, and <x-profile-settings>. Every element accepts 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 they clicked it on. Give it a
redirect-uri to send them somewhere else instead, which is what a marketing
page handing off to your app wants:
<x-signin-button redirect-uri="https://app.example.com/"></x-signin-button>The target must be an absolute URL whose origin is on the tenant's allowlist. The server validates it and falls back to the app URL, so a value it does not trust is ignored rather than honored.
<x-authorized> and <x-non-authorized> are older spellings of <x-signed-in> and
<x-signed-out>. Both are supported, and neither is preferred.
Triggering Userspace from your own markup
<x-trigger> wraps your own element, rendering nothing of its own, and fires a
Userspace action when it is clicked (a tap counts as a click). The actions are
settings, which opens the settings popup, sign-in, which leaves for the hosted
sign-in page, and sign-out, which signs the user out. Wrap a real interactive
element, like a button or your design system's menu item, so keyboard activation
comes for free, and place the trigger inside <x-signed-in>: without a session
settings logs a console warning and opens nothing.
sign-in and sign-out take the same redirect-uri attribute as
<x-signin-button>, naming where to land afterwards.
<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>Every action is available imperatively. All settings triggers and callers share one popup instance.
await Userspace.openSettings()
await Userspace.signOut()signIn() leaves for the hosted sign-in page, carrying redirect_uri back to
the current URL so the user returns where they were — the same navigation the
autoLogin option performs, on a click instead of on every signed-out visit.
Pass redirectUri to land somewhere else, under the same allowlist rule as
signOut:
Userspace.signIn()
Userspace.signIn({ redirectUri: 'https://app.example.com/' })signOut() ends every session in this browser, not only the current one: the
server terminates the whole cookie-scoped session, so other tabs sign out too.
It POSTs to the sign-out endpoint, then navigates to the hosted sign-in page —
or to redirectUri, an absolute URL whose origin must be on the tenant's
allowlist (the server validates it; untrusted targets fall back to sign-in):
await Userspace.signOut({ redirectUri: 'https://app.example.com/logged-out' })registerElements() registers the custom elements without configuring the SDK, for
hosts that configure another way. configureUserspace already calls it; most pages
never need it directly.
License
MIT.
