threshold1

Node

This is the narrowest of the framework guides, on purpose. "Plain JS" means a browser with no framework; this page means no browser at all. Read the boundary section before writing any server-side code against this SDK — it will save you from architecting around something that isn't there.

The boundary, stated plainly, up front

You cannot construct a Threshold1 instance in your backend and hand it an already-logged-in browser user's JWT to "continue" or "check" their session. There is no constructor option for an existing token, no setter, no way in. Every session-bound method (getUser(), getSession(), addPasskey(), enrollTotp(), and the rest) requires a JWT that was set by that exact instance completing a real auth flow — a login or register call it ran itself, in its own process lifetime. A fresh instance, no matter how it's configured, starts with no session and has no way to be given one from outside.

This is an architectural boundary, not a missing feature. If you need to verify a user's session from your own backend — confirm a request really came from a logged-in user, look up who they are — do that by verifying their JWT directly against threshold1's API. Don't try to reconstruct an SDK instance around a token you got from somewhere else; that path doesn't exist, and nothing about this SDK's design is trying to grow one.

What this guide actually covers

Node usage of this SDK is for processes that complete a full auth flow themselves, for their own purposes, within one process lifetime — not for relaying or continuing someone else's browser session. Real examples: a CLI tool that verifies a user by email before running a privileged command, a backend job that sends a verification code as part of some other workflow, a support bot that needs to confirm the person messaging it owns the email they claim.

What works — verified live in plain Node

Confirmed directly, no browser shims of any kind (typeof window, typeof document, typeof localStorage all undefined; only fetch present):

import { Threshold1 } from "@threshold1/auth";

const auth = new Threshold1({ apiKey: "th_test_..." });

await auth.sendOtp("user@example.com");
// Real network call — no window/document involved at all. Resolved cleanly.

// ... your process gets the code back some other way (however this
// specific tool collects it) ...

const user = await auth.verifyOtp("user@example.com", "123456");
// Real UserProfile: { id, email, externalUserId }

const fresh = await auth.getUser();
// Works — THIS SAME instance just authenticated itself via verifyOtp()
// above. This is the one and only way getUser() works in Node: call it
// on the instance that did the logging in, in the same process run.

This exact round trip was live-verified end to end against the real hosted API: real email sent, real code retrieved, real session established, getUser() on that same instance returning the real, matching profile.

loginWithMagic()/registerWithMagic()'s send half works the same way — a plain network call, nothing browser-dependent:

await auth.loginWithMagic("user@example.com");
// Resolves OK — queues a real email. auth.isAuthenticated stays false;
// this call was never going to establish a session (see Magic Link),
// so there's nothing further this Node process can do with it. The
// link itself has to be opened in a real browser to complete.

The boundary, confirmed live — not just read from source

A fresh instance, in a process that hasn't logged in yet, really does fail the way described above. Captured the actual error, not assumed it:

const auth = new Threshold1({ apiKey: "th_test_..." });
// isAuthenticated: false

await auth.getUser();
// throws: "Threshold1 SDK: not authenticated. Call login() or
// register() first, or call resumeSession() on page load to restore
// a magic link session."

That message's own wording — "call login() or register() first" — is the boundary in one sentence: there is no other door in.

What's next

  • Plain JS — the browser counterpart to this guide, for when there is a real browser and a real user in front of it.
  • Email OTP — the full real values (TTL, attempts, rate limits) behind the round trip shown above.