Skip to main content

Authenticate with the JavaScript SDK

Browser apps built with @taruvi/sdk don't handle passwords. Users sign in on TaruviBase's hosted sign-in page, and the SDK keeps the session that comes back. This guide adds sign-in and sign-out to a browser app, shows how to check the session, and covers server code.

It assumes the browser client from JavaScript SDK.

How browser sign-in works#

  1. auth.login() sends the browser to /accounts/login/ on your site, with redirect_to set to the page to return to.
  2. The user signs in with a password or a configured SSO provider.
  3. TaruviBase redirects to redirect_to and adds #session_token=… to the address, along with short-lived JWTs (access_token, refresh_token).
  4. When your app constructs Client on that page, the SDK saves the token in localStorage, under the key session_token, and removes the sign-in values, JWTs included, from the address bar.
  5. Every later request sends the token in the X-Session-Token header.

Add sign-in and sign-out#

Create the Auth service from your client and gate the app on the session:

src/auth.ts
import {Auth} from '@taruvi/sdk';
import {taruvi} from './taruvi';

export const auth = new Auth(taruvi);

export async function requireSignedInUser(): Promise<void> {
if (await auth.isUserAuthenticated()) return;
// Leaves the page. The user comes back here after signing in.
auth.login();
}

export function signOut(): Promise<void> {
return auth.logout();
}

Import taruvi before you read the session on the page users return to. Constructing the client is what captures the token from the address.

  • login(callbackUrl?) — Opens hosted sign-in. The user returns to callbackUrl, an absolute URL on your app, or by default to the current origin and path, without the query string and fragment.
  • signup(callbackUrl?) — Opens hosted sign-up, when your site allows self sign-up. The user returns the same way as with login().
  • logout(callbackUrl?) — Clears the stored token, then opens /accounts/logout/ to end the TaruviBase session. The default callbackUrl is the app's origin.

login() and signup() only work in a browser; elsewhere, such as during server-side rendering, they log an error and do nothing. Outside a browser, logout() only clears the stored token. Call them from client code, such as an event handler.

TaruviBase ends the session and returns the user to callbackUrl only when it accepts that address as a redirect target. Otherwise it shows its own sign-out page, and the TaruviBase session stays active until the user confirms there, so the next login() signs them straight back in. Test sign-out from each domain your app runs on.

Use a separate sign-in host#

The hosted pages live on apiUrl by default. If your site serves them from another host, set deskUrl on the client. login(), signup(), and logout() all use it:

const taruvi = new Client({
apiUrl: 'https://YOUR_SITE_HOST',
deskUrl: 'https://YOUR_SIGN_IN_HOST',
appSlug: 'APP_SLUG',
});

Check and manage the session#

These methods read the stored session without a request:

  • hasToken() — true when a token is stored. It doesn't prove the token is still valid.
  • getSessionToken() — The stored token, or null.

These ask TaruviBase:

  • isUserAuthenticated() — Resolves false when there is no token or TaruviBase rejects it, with GET /_allauth/app/v1/auth/session.
  • validateSession() — The same request, but rejects with AuthError when the session is missing, expired, or ended.
  • getCurrentUser() — The user envelope from GET /api/users/me/, or null when there is no token or the request fails.

And these change it:

  • handleRedirect(url?) — Captures #session_token from the address, stores it, and returns it, or null. Use it with detectSessionInUrl: false.
  • setSession(token) — Stores a session token your app got elsewhere.
  • clearSession() — Forgets the stored token without redirecting.

Use hasToken() to render quickly, then confirm with isUserAuthenticated() before you show private data. getCurrentUser() logs and swallows its own errors; call validateSession() when you need the failure.

Where the session lives#

  • In a browser, the token is in localStorage under session_token, shared by every tab on the origin. It's cleared when logout() runs or TaruviBase answers 401, 410, or 419.
  • In Node.js and React Native, it's in memory, on the Client instance you passed token to. It's cleared when the client is discarded or TaruviBase answers 401, 410, or 419.
Protect the stored session

Any script running on your origin can read localStorage. A cross-site scripting bug therefore exposes the user's TaruviBase session. Set a strict Content Security Policy, avoid injecting untrusted HTML, and keep third-party scripts to a minimum.

Call TaruviBase from a server#

Server code acts either as a signed-in user or, with an API key, as the key's creator.

Send the browser's session token to your own backend, for example in a request header of your choosing:

src/tasks-api.ts
import {auth} from './auth';

export async function fetchTasks() {
const response = await fetch('/api/tasks', {
headers: {'X-Taruvi-Session': auth.getSessionToken() ?? ''},
});
return response.json();
}

On the server, create a client for that request:

src/server/tasks.ts
import {Client, Database} from '@taruvi/sdk';

export async function listTasksFor(sessionToken: string) {
const taruvi = new Client({
apiUrl: process.env.TARUVI_SITE_URL!,
appSlug: process.env.TARUVI_APP_SLUG!,
token: sessionToken,
});

return new Database(taruvi).from('tasks').pageSize(20).execute();
}

TaruviBase applies that user's roles and access policies to every request. Treat the token like a password: send it only over HTTPS and don't log it.

Keep API keys out of browser code

Anyone holding an API key can act as the user who created it. Never put TARUVI_API_KEY in a public build variable such as VITE_*, NEXT_PUBLIC_*, or EXPO_PUBLIC_*.

Server-rendered apps (Next.js)#

A framework that renders on the server, such as Next.js, needs two changes: it captures the sign-in redirect after hydration, and it hands the session to the server in a cookie so server components can act as the user.

Create the browser client with detectSessionInUrl: false, so creating it doesn't touch the address:

lib/taruvi-browser.ts
import {Auth, Client} from '@taruvi/sdk';

export const taruvi = new Client({
apiUrl: process.env.NEXT_PUBLIC_TARUVI_SITE_URL!,
appSlug: process.env.NEXT_PUBLIC_TARUVI_APP_SLUG!,
detectSessionInUrl: false,
});

export const auth = new Auth(taruvi);

Capture the session in a client component rendered on the page users return to, and send it to a route handler that stores it in an httpOnly cookie:

app/session-capture.tsx
'use client';

import {useRouter} from 'next/navigation';
import {useEffect, useState} from 'react';
import {auth} from '../lib/taruvi-browser';

export function SessionCapture() {
const router = useRouter();
const [failed, setFailed] = useState(false);

useEffect(() => {
const sessionToken = auth.handleRedirect();
if (!sessionToken) return;
fetch('/api/session', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({sessionToken}),
})
.then((response) => {
if (!response.ok) throw new Error(`Session route answered ${response.status}`);
// Re-render the Server Components, which now see the session cookie.
router.refresh();
})
.catch(() => setFailed(true));
}, [router]);

if (!failed) return null;
return (
<p role="alert">
Sign-in couldn't be completed.{' '}
<button type="button" onClick={() => auth.login()}>
Try again
</button>
</p>
);
}
app/api/session/route.ts
import {cookies} from 'next/headers';

export async function POST(request: Request) {
// Only accept the token from your own pages.
if (request.headers.get('origin') !== new URL(request.url).origin) {
return new Response(null, {status: 403});
}
const {sessionToken} = (await request.json()) as {sessionToken?: string};
if (!sessionToken) return new Response(null, {status: 400});
(await cookies()).set('taruvi_session', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
});
return new Response(null, {status: 204});
}

export async function DELETE() {
(await cookies()).delete('taruvi_session');
return new Response(null, {status: 204});
}

Server components and route handlers then create a client for the request:

lib/taruvi-server.ts
import 'server-only';
import {cookies} from 'next/headers';
import {Client} from '@taruvi/sdk';

export async function taruviForRequest(): Promise<Client> {
return new Client({
apiUrl: process.env.TARUVI_SITE_URL!,
appSlug: process.env.TARUVI_APP_SLUG!,
token: (await cookies()).get('taruvi_session')?.value,
});
}

On sign-out, call DELETE /api/session before auth.logout(). When your app already has the token from somewhere else, auth.setSession(token) stores it, and auth.clearSession() forgets it without redirecting.

Allow your app's address#

Browsers only let your app call TaruviBase from addresses TaruviBase allows for cross-origin requests. By default these are:

  • https://*.taruvi.app, https://*.taruvi.cloud, and https://*.taruvi.space
  • http://localhost:3000 and http://localhost:5173, for development

From any other address, every request fails with NetworkError and a statusCode of 0, and the browser console reports a CORS error. Contact TaruviBase support to allow your own domain. Server code isn't affected.

Troubleshooting#

The user signs in but the app still shows them signed out. The page they return to must construct Client before it checks the session. Import your client module early on that page.

Requests start failing with AuthError after a while. The session expired or was ended (401, 410, or 419), and the SDK has already cleared it. Call auth.login() again.

Every request fails with NetworkError after you deploy. The app's new address isn't allowed for cross-origin requests. See Allow your app's address.

login() can only be called in browser environment appears in the console. The call ran during server-side rendering. Move it into client-only code.

Next steps#