Documentation
ReferenceJavaScript SDKNamespacesauth

auth

Sign-up, sign-in, OAuth, tokens and session checks.

The top-level namespace. Everything here is available on both clients, but the browser client also stores what it gets back — a successful signIn leaves the session on disk and the token refreshing in the background, while the same call on the server returns tokens and forgets them.

Where a method needs a session, an Authorization header you already supply wins — per call in options.headers, or for every call through the client's headers option, in any letter case, so the lowercase one forwardUserHeaders produces works as is. Otherwise the browser client reads the token from storage. Neither guesses: a call with no token available fails locally with no_session and never leaves the process.

signUp

signUp(params) → Authenticated

Creates a user. What comes back depends on how the environment confirms accounts, so branch on the response rather than assuming.

const { data, error } = await nulvless.auth.signUp({
  email: '[email protected]',
  password,
  profile: { name: 'Ada Lovelace' },
});
emailstringOptional

Required unless phone is given.

phonePhoneOptional

{ country_code, number } — not a formatted string.

usernamestringOptional

3–30 characters: letters, numbers, dots, underscores. Not sufficient on its own — sign-up always needs an email or a phone as well.

passwordstringRequired

Checked against the environment's length, character and breach rules.

profileUserProfileOptional

Name, avatar and the rest of the person's details.

Returns Authenticated. Read challenge first — the account is not usable until confirmation completes, and with code confirmation the user record does not exist yet at all. See Sign-up and verification for the full branch.

A single sign-up can be rejected for several fields at once, in which case error.message is an array. Handle that centrally rather than at each form.


signIn

signIn(params) → Authenticated

Password sign-in. Identify the user by whichever credential your product uses.

const { data, error } = await nulvless.auth.signIn({ email, password });

if (error) return showError();
if (data.challenge) return goToVerify(data.challenge);
emailstringOptional
One of these three is required.
phonePhoneOptional
usernamestringOptional
passwordstringRequired
optionsAuthOptionsOptional

Per-call overrides, including the captcha token when the environment requires one for sign-in.

Two outcomes, in order:

  • data.challenge — the user has a second factor, or the environment requires one. Nothing is signed in yet; carry the challenge to verifyChallenge.
  • data.access_token — signed in. The browser client has already stored the session and emitted signed-in.

A wrong password answers 401 invalid_credentials. Do not distinguish "no such user" from "wrong password" in your copy — the platform deliberately does not.


signInWithOTP

signInWithOTP(params) → Challenge

Passwordless sign-in: sends a code and returns the challenge to collect it with.

const { data: challenge } = await nulvless.auth.signInWithOTP({
  email: '[email protected]',
});
emailstringOptional
One of these two is required.
phonePhoneOptional
optionsAuthOptionsOptional

Answers 202 Accepted with a Challenge, not a session — the session only appears after verifyChallenge. The method must be enabled for the environment, or the call is refused.

Because it sends a message, this call is rate limited.


lookup

lookup(params) → LookupResult

Asks what a given identifier can do, before asking for a password.

const { data } = await nulvless.auth.lookup({ email });

if (data.challenge) return goToVerify(data.challenge);
emailstringOptional
One of these three is required.
phonePhoneOptional
usernamestringOptional

This is what an adaptive sign-in screen is built from: collect the email, ask the platform, then render either a password field or a code field instead of guessing. data.lookup describes the match.

lookup may return a challenge already started when the user's only method is OTP — a code has been sent. Check for it before rendering a password field, or you will send a second code when the user submits.


authorize

authorize(provider, options) → { url, provider }

Starts a social sign-in.

await nulvless.auth.authorize('google', {
  redirect_uri: `${window.location.origin}/callback`,
});
providerOAuthProviderRequired

Must also be enabled for the environment.

google
github
gitlab
bitbucket
facebook
linked_inNote the underscore.
discord
twitch
slack
azure
apple
options.redirect_uristringOptional

Where the flow lands when it finishes. Must be on the environment's allowlist, or the request is rejected before any token exists.

In a browser it redirects for you — the returned URL is assigned to window.location and the promise never usefully resolves. On the server nothing is redirected; use data.url yourself:

const { data } = await nulvless.auth.authorize('google', {
  redirect_uri: 'https://app.example.com/callback',
});

return Response.redirect(data.url);

Prefer rendering provider buttons from getMethods() rather than hard-coding them, so enabling one in the console needs no redeploy.


connect

connect(provider, options) → { url, provider }

The same OAuth redirect, for a user who is already signed in. The provider identity is linked to the current account instead of starting a new sign-in.

await nulvless.auth.connect('github', {
  redirect_uri: `${window.location.origin}/settings/connections`,
});

Requires a session. This is the call behind a "connected accounts" screen — and the reason a user can end up with a password, a Google identity and a GitHub identity all resolving to one id.


signInWithIdToken

signInWithIdToken(provider, idToken) → Authenticated

Signs in with an ID token you already obtained natively — a mobile app using the Google Sign-In SDK, for instance, where the redirect dance has already happened outside the browser.

const { data } = await nulvless.auth.signInWithIdToken('google', googleIdToken);

Stores the session automatically when one comes back.


exchangeCodeForToken

exchangeCodeForToken(code) → Authenticated

Exchanges an OAuth authorization code for a session (grant_type=authorization_code).

const code = new URLSearchParams(location.search).get('code');
if (code) await nulvless.auth.exchangeCodeForToken(code);
codestringRequired

From the code query parameter on your callback URL.

You need this only when you handle the callback yourself. The browser flow started by authorize is exchanged for you.


verifyChallenge

verifyChallenge({ token, code }) → Authenticated

Submits the code for any challenge — OTP sign-in, MFA after a password, or a sign-up confirmation. One call closes all three.

const { data } = await nulvless.auth.verifyChallenge({
  token: challenge.token,
  code,
});

Alias for auth.challenges.verify, which is also where the attempt limit and the rest of the challenge lifecycle are described. Session is stored automatically on success.


activate

activate(token) → null

Confirms an account from the link emailed during sign-up.

const token = new URLSearchParams(location.search).get('token');
await nulvless.auth.activate(token);
tokenstringRequired

From the token query parameter on your confirmation page.

Returns nothing — no session. Confirming by link proves the address; it does not sign the person in. Send them to sign-in afterwards.


recover

recover({ email }) → null

Sends a password-recovery email.

await nulvless.auth.recover(
  { email },
  { redirect_uri: 'https://app.example.com/reset' },
);
emailstringRequired
options.redirect_uristringOptional

Where the link in the email lands.

Resolves the same way whether or not the address exists. That is deliberate — the response must not tell an attacker which addresses are registered — so your UI should say "if that address is registered, we sent a link" rather than confirming anything.


resetPassword

resetPassword({ token, password }) → null

Completes the recovery started above.

await nulvless.auth.resetPassword({ token, password: newPassword });
tokenstringRequired

From the recovery link.

passwordstringRequired

Checked against the same rules as sign-up.

For a user who is signed in and simply wants a new password, use auth.security.changePassword instead — it requires the current password, which this path cannot.


getMethods

getMethods() → AuthMethods

Everything the current user can authenticate with, on this environment.

const { data } = await nulvless.auth.getMethods();
// data.sign_in · data.mfas · data.oauth

Requires a session. Returns three lists:

sign_inAuthMethod[]Optional

Password, OTP and anything else enabled for signing in.

mfasAuthMethod[]Optional

Second factors, each with a status of configured or not_configured. Note the field is mfas, plural.

oauthAuthMethod[]Optional

Social providers, including the ones this user has not connected.

Two screens are built from this: the provider buttons on sign-in, and the security settings page listing what is set up and what is still available.


getSession

getSession() → Session

The current session — device, location, the methods used to reach it, and whether sudo is active.

const { data: session, error } = await nulvless.auth.getSession();

Requires a session, and fails locally with no_session when there is none, so it doubles as "is anyone signed in?" without a network round trip when storage is empty.


getAccessToken

getAccessToken() → string | null

The current access token, refreshed first if it is expired or close to it.

This is the one method that does not return a NulvlessResult — it resolves to the token string, or null when there is no session. There is no error to check.

Call it per request rather than caching the result. Holding the string defeats the refresh, and a token that was valid when you stored it may not be when you send it:

const token = await nulvless.auth.getAccessToken();

await fetch('/api/projects', {
  headers: token ? { Authorization: `Bearer ${token}` } : {},
});

refreshToken

refreshToken({ refresh_token }) → Authenticated

Exchanges a refresh token for a new session, and updates storage.

await nulvless.auth.refreshToken({ refresh_token: stored.refresh_token });

The browser client does this for you — see SessionManager. Call it directly when you hold a refresh token yourself, typically on a server that persisted one.


verify

verify(grant?, options) → Verified

Answers two questions in one call: is this token still valid, and does its holder have the permission you named.

resourcestringOptional

Which resource the question is about. Omit to check the token only.

scopesstring[]Optional

The holder must have all of them.

rolesstring[]Optional

Matched against the role's key. Any one is enough. Ignored when no resource is given.

Returns Verified — the user id, and a session carrying the resolved scopes for that resource, the amr of how they authenticated, and whether sudo is active.

const { data, error } = await nulvless.auth.verify(
  { resource: 'acme', scopes: ['members:write'] },
  { headers: { Authorization: `Bearer ${token}` } },
);

if (error?.status_code === 401) return signInAgain();
if (error?.status_code === 403) return forbidden();

Role-based access control covers what the two dimensions mean and why you should usually ask for a scope.


signOut

signOut(scope?) → null

Ends the session and clears local storage.

await nulvless.auth.signOut();          // this device
await nulvless.auth.signOut('others');  // everywhere else
await nulvless.auth.signOut('global');  // everywhere
scopeSignOutScopeOptional

Which sessions to end. Defaults to local.

localOnly this one. The default.
globalEvery session this account has, on every device.
othersEvery session except this one.

global is what a "sign out everywhere" button does after a password change. others is the safer version of the same idea when the user is on the device they trust.

Storage is cleared only when the request succeeds, so a failed sign-out leaves the client in a consistent state rather than locally signed out and remotely alive.

Captcha

When the environment protects an endpoint with a captcha, the call needs a token in options.captcha_token. It applies to lookup and signInWithOTP, signIn, signUp and recover, each one only if that group is turned on in the environment's captcha settings.

You do not have to show the widget up front. Call without a token, and only when the answer is captcha_required or captcha_verification_failed show the captcha and repeat the call with the token it returns:

const signIn = (captcha_token?: string) =>
  nulvless.auth.signIn({ email, password, options: { captcha_token } });

let result = await signIn();

if (result.error?.code === 'captcha_required' || result.error?.code === 'captcha_verification_failed') {
  const token = await showCaptcha();
  result = await signIn(token);
}

showCaptcha is your Turnstile or hCaptcha widget, configured with the site key that pairs with the secret set in the environment. A token is accepted once, so render the widget again for every new attempt.

Next

On this page