Skip to content

Pass routeConfig as the third argument to bridgeBootstrap in +layout.ts. The BridgeBootstrap component in +layout.svelte handles navigation guards automatically.

src/routes/+layout.ts
import type { LayoutLoad } from './$types';
import type { BridgeConfig, RouteGuardConfig } from '@nebulr-group/bridge-svelte';
import { bridgeBootstrap } from '@nebulr-group/bridge-svelte';
export const ssr = false;
export const load: LayoutLoad = async ({ url }) => {
const config: BridgeConfig = {
appId: import.meta.env.VITE_BRIDGE_APP_ID,
loginRoute: '/auth/login',
};
const routeConfig: RouteGuardConfig = {
rules: [
{ match: '/', public: true },
{ match: new RegExp('^/auth($|/)'), public: true },
{ match: '/beta/*', featureFlag: 'beta-feature', redirectTo: '/' },
],
defaultAccess: 'protected',
};
await bridgeBootstrap(url, config, routeConfig);
return {};
};
src/routes/+layout.svelte
<script lang="ts">
import { BridgeBootstrap } from '@nebulr-group/bridge-svelte';
let { children } = $props();
let ready = $state(false);
function onBootstrapComplete() {
ready = false;
// The tick after BridgeBootstrap resolves, render children
ready = true;
}
</script>
<BridgeBootstrap {onBootstrapComplete} />
{#if ready}
{@render children()}
{/if}

How it works:

  • defaultAccess — sets whether unmatched routes are 'public' or 'protected'.
  • rules — marks individual paths as public and/or gates them behind feature flags.
  • loginRoute — unauthenticated users are redirected here (in-app) instead of to an external page.
  • Redirects are handled automatically by BridgeBootstrap.

Use the reactive stores to check whether the user is authenticated:

<script lang="ts">
import { isAuthenticated, isLoading } from '@nebulr-group/bridge-svelte';
</script>
{#if $isLoading}
<p>Loading...</p>
{:else if $isAuthenticated}
<p>You are logged in!</p>
{:else}
<p>Please log in to continue.</p>
{/if}

Access the current user’s profile with profileStore:

<script lang="ts">
import { profileStore } from '@nebulr-group/bridge-svelte';
const { profile } = profileStore;
</script>
{#if $profile}
<h2>{$profile.fullName}</h2>
<p>{$profile.email}</p>
{#if $profile.tenant}
<p>Tenant: {$profile.tenant.name}</p>
{/if}
{:else if $profile === undefined}
<p>Loading profile...</p>
{:else}
<p>Not logged in</p>
{/if}

profileStore is undefined while loading, null when not authenticated, and a profile object when authenticated.

A live snapshot of the signed-in user (id, email, role, tenantId) plus workspace and subscription state is also available on the unified bridge surface (bridge.user, bridge.tenant.*), kept current over the live channel — see Live Updates & the Bridge Surface.

A drop-in component that renders the user’s display name. No configuration needed:

<script lang="ts">
import { ProfileName } from '@nebulr-group/bridge-svelte';
</script>
<ProfileName />
<!-- renders: "John Doe" or "john@example.com" or nothing when not authenticated -->

Outputs a <span> with a data-bridge-profile-name attribute for styling. Accepts class and style props.

Bridge automatically refreshes tokens before they expire. No manual intervention is needed. The auth.token store always holds the current valid token set.

Call getBridgeAuth().logout() to clear tokens. Pass redirectTo to stay in the app after logout:

<script lang="ts">
import { getBridgeAuth } from '@nebulr-group/bridge-svelte';
import { goto } from '$app/navigation';
async function handleLogout() {
await getBridgeAuth().logout({ redirectTo: '/' });
}
</script>
<button onclick={handleLogout}>Log out</button>

If you omit redirectTo, the user is redirected externally to complete logout.


All SDK auth components are in-app — they render inside your application with no external redirects. Import them from @nebulr-group/bridge-svelte.

Every component accepts standard HTML div (or button) attributes like class, style, and data-* in addition to the props listed below.

A complete login form with email/password fields. Handles multi-step auth flows inline: forgot password, magic link, passkey login, MFA challenge, MFA setup, and tenant selection all appear automatically within the same component when the auth state requires them.

Props:

PropTypeDefaultDescription
showSignupLinkbooleanfalseShow a link to the signup page
signupHrefstring'/signup'Signup page URL
showForgotPasswordbooleantrueShow the forgot password link
forgotPasswordHrefstringundefinedExternal forgot password URL. If set, navigates there instead of showing the inline form
showMagicLinkbooleantrueShow the magic link login option
showPasskeysbooleanfalseShow the passkey login button
onLogin() => voidCalled after successful login (all steps complete)
onError(error: Error) => voidCalled on any login error
onSsoClick(connectionType: string) => voidCalled when an SSO button is clicked
headingstring''Custom heading text
ssoConnectionsFederationConnection[][]SSO connections to display. Auto-derived from app config if not set
ssoMode'redirect' | 'popup''redirect'SSO kickoff strategy for the built-in buttons. See SSO mode. Ignored when onSsoClick is provided.
footerSnippetCustom footer content (Svelte 5 snippet)

Usage:

<script lang="ts">
import { goto } from '$app/navigation';
import { LoginForm } from '@nebulr-group/bridge-svelte';
</script>
<LoginForm
showSignupLink
signupHref="/auth/signup"
showForgotPassword
showMagicLink
showPasskeys
onLogin={() => goto('/dashboard')}
onError={(err) => console.error(err)}
/>

Auth state transitions: After a successful email/password login, the LoginForm checks the resulting auth state. If MFA is required, it automatically shows MfaChallenge. If MFA setup is required, it shows MfaSetup. If tenant selection is needed (multi-tenant user), it shows TenantSelector. The onLogin callback fires only after all steps are complete and the user is fully authenticated.

A signup form with email, full name, and password fields.

Props:

PropTypeDefaultDescription
onSignup() => voidCalled after successful signup
onError(error: Error) => voidCalled on signup error
showLoginLinkbooleantrueShow a link to the login page
loginHrefstring'/login'Login page URL
headingstring'Create your account'Custom heading text
footerSnippetCustom footer content

Usage:

<script lang="ts">
import { goto } from '$app/navigation';
import { SignupForm } from '@nebulr-group/bridge-svelte';
</script>
<SignupForm
showLoginLink
loginHref="/auth/login"
onSignup={() => goto('/auth/login')}
onError={(err) => console.error(err)}
/>

After signup, the user receives a verification email. Once verified, they can log in.

A standalone SSO login button for a single federation connection. Use this when you want SSO buttons outside of LoginForm, or to build a custom login page.

Props:

PropTypeDefaultDescription
connectionFederationConnection(required)The SSO connection object
labelstring'Continue with {name}'Button label text
mode'redirect' | 'popup''redirect'SSO kickoff strategy. See SSO mode.
onSuccess() => voidCalled after successful SSO login (popup mode only)
onError(error: Error) => voidCalled on error
iconSnippetCustom icon snippet

Usage:

<script lang="ts">
import { SsoButton, type FederationConnection } from '@nebulr-group/bridge-svelte';
let { connections }: { connections: FederationConnection[] } = $props();
</script>
{#each connections as connection}
<SsoButton
{connection}
onSuccess={() => console.log('SSO login complete')}
onError={(err) => console.error(err)}
/>
{/each}

Both LoginForm and standalone SsoButton support two SSO kickoff strategies via the ssoMode / mode prop:

ModeWhat happensWhen to use
'redirect' (default)Clicking the button navigates the current tab to the Bridge federation endpoint. The user is sent to the provider (Google, Microsoft, etc.), signs in, and the OAuth callback chain returns them to your app via the normal route guard flow. No popup, no cross-window messaging.Almost all apps. This is the safest, most compatible default — pop-up blockers, mobile browsers, embedded webviews, and strict CSPs all work out of the box. The route guard automatically completes the auth transition when the user lands back on a protected route.
'popup'Clicking the button opens window.open() to the Bridge federation endpoint with mode=popup. The popup completes the provider flow and postMessage’s the result back to the opener, which resolves the startSsoLogin() promise. The host page never unloads.Embedded widgets, multi-tab dashboards, or flows that must preserve unsaved state on the host page. Requires targetOrigin to match your app origin (handled automatically). Pop-up blockers may interfere — handle onError for the “popup blocked” case.

Redirect mode example:

<LoginForm />
<!-- or explicitly -->
<LoginForm ssoMode="redirect" />
<SsoButton {connection} />
<!-- or explicitly -->
<SsoButton {connection} mode="redirect" />

In redirect mode, onSuccess / onLogin do not fire on the original page — it’s already navigating away. Instead, rely on your route guard + authState store transitions to pick up the session once the user lands back in your app.

Popup mode example:

<LoginForm ssoMode="popup" />
<SsoButton
{connection}
mode="popup"
onSuccess={() => console.log('popup auth complete')}
onError={(err) => {
if (err.message.includes('popup')) {
// popup was blocked — prompt the user to allow popups
}
}}
/>

In popup mode, the promise returned by startSsoLogin() resolves with the final auth result (or rejects if the popup is blocked, closed, or times out after 5 minutes), so onSuccess and onError fire as expected.

Under the hood: both modes hit the same backend endpoint GET /auth/auth/federation/:appId?provider=<type>. Popup mode additionally sends mode=popup&targetOrigin=<origin> query params, which the backend uses to route the final callback into a postMessage instead of a normal redirect.

Prompts the user to enter an MFA code. Appears automatically inside LoginForm when authState transitions to 'mfa-required'. Can also be used standalone.

Props:

PropTypeDefaultDescription
onVerified() => voidCalled after successful MFA verification
onError(error: Error) => voidCalled on verification error
showRecoveryOptionbooleantrueShow the recovery code toggle

The component supports two modes:

  1. Auth code — the user enters a 6-digit code from their authenticator app.
  2. Recovery code — the user enters a backup recovery code.

Standalone usage:

<script lang="ts">
import { MfaChallenge, authState } from '@nebulr-group/bridge-svelte';
import { goto } from '$app/navigation';
</script>
{#if $authState === 'mfa-required'}
<MfaChallenge
onVerified={() => goto('/dashboard')}
onError={(err) => console.error(err)}
/>
{/if}

Guides the user through a 3-step MFA setup flow: enter phone number, verify code, save backup codes. Appears automatically inside LoginForm when authState transitions to 'mfa-setup-required'.

Props:

PropTypeDefaultDescription
onComplete() => voidCalled after MFA setup is complete
onError(error: Error) => voidCalled on setup error

Standalone usage:

<script lang="ts">
import { MfaSetup, authState } from '@nebulr-group/bridge-svelte';
import { goto } from '$app/navigation';
</script>
{#if $authState === 'mfa-setup-required'}
<MfaSetup
onComplete={() => goto('/dashboard')}
onError={(err) => console.error(err)}
/>
{/if}

Lets multi-tenant users pick which tenant to log into. Appears automatically inside LoginForm when authState transitions to 'tenant-selection'.

Props:

PropTypeDefaultDescription
onSelect() => voidCalled after tenant is selected
onError(error: Error) => voidCalled on error
tenantItemSnippet<[TenantUser]>Custom render snippet for each tenant item

Standalone usage with custom tenant item:

<script lang="ts">
import { TenantSelector, authState, type TenantUser } from '@nebulr-group/bridge-svelte';
import { goto } from '$app/navigation';
</script>
{#if $authState === 'tenant-selection'}
<TenantSelector onSelect={() => goto('/dashboard')}>
{#snippet tenantItem(tenant: TenantUser)}
<div class="tenant-card">
<strong>{tenant.tenantName}</strong>
<span>{tenant.role}</span>
</div>
{/snippet}
</TenantSelector>
{/if}

Standalone magic link request form. When a user clicks a magic link from their email, the token is in the URL and Bridge processes it automatically during bootstrap.

Props:

PropTypeDefaultDescription
onSent() => voidCalled after the magic link email is sent
onError(error: Error) => voidCalled on error
loginHrefstring'/login'Link back to the login page

Usage:

src/routes/auth/magic-link/+page.svelte
<script lang="ts">
import { MagicLink } from '@nebulr-group/bridge-svelte';
</script>
<MagicLink
loginHref="/auth/login"
onSent={() => console.log('Check your email!')}
onError={(err) => console.error(err)}
/>

When the user clicks the link in their email, they are brought to your app. The token URL parameter is auto-handled by Bridge bootstrap.

Dual-mode component:

  1. Request mode (no token prop) — shows an email form to request a password reset link.
  2. Reset mode (token prop set) — shows a new password form to complete the reset.

Props:

PropTypeDefaultDescription
tokenstringReset token from URL. When set, shows the new password form
onComplete() => voidCalled after the email is sent (request mode) or password is reset (reset mode)
onError(error: Error) => voidCalled on error
loginHrefstring'/login'Link back to the login page

Request page:

src/routes/auth/forgot-password/+page.svelte
<script lang="ts">
import { ForgotPassword } from '@nebulr-group/bridge-svelte';
</script>
<ForgotPassword
loginHref="/auth/login"
onComplete={() => console.log('Reset email sent')}
/>

Reset page (with token from URL):

src/routes/auth/reset-password/+page.svelte
<script lang="ts">
import { page } from '$app/stores';
import { ForgotPassword } from '@nebulr-group/bridge-svelte';
import { goto } from '$app/navigation';
const token = $derived($page.url.searchParams.get('token') ?? '');
</script>
<ForgotPassword
{token}
loginHref="/auth/login"
onComplete={() => goto('/auth/login')}
/>

Three components for passkey (WebAuthn) authentication. Requires @simplewebauthn/browser as a peer dependency.

A button that triggers passkey authentication via the browser’s WebAuthn API.

Props:

PropTypeDefaultDescription
onLogin() => voidCalled after successful passkey login
onError(error: Error) => voidCalled on error
onSetupPasskey() => voidCalled when the user wants to set up a passkey instead
autofillbooleanfalseUse WebAuthn conditional UI (autofill)
<script lang="ts">
import { PasskeyLogin } from '@nebulr-group/bridge-svelte';
import { goto } from '$app/navigation';
</script>
<PasskeyLogin
onLogin={() => goto('/dashboard')}
onError={(err) => console.error(err)}
/>

Registers a new passkey using a setup token (emailed to the user).

Props:

PropTypeDefaultDescription
tokenstring(required)The setup token from the URL
onComplete() => voidCalled after passkey registration
onError(error: Error) => voidCalled on error
onBack() => voidCalled when user clicks back
onExpired() => voidCalled when the token has expired
src/routes/auth/passkey-setup/+page.svelte
<script lang="ts">
import { page } from '$app/stores';
import { PasskeySetup } from '@nebulr-group/bridge-svelte';
import { goto } from '$app/navigation';
const token = $derived($page.url.searchParams.get('token') ?? '');
</script>
<PasskeySetup
{token}
onComplete={() => goto('/auth/login')}
onExpired={() => goto('/auth/login')}
/>

An email form that requests a passkey setup link be sent to the user.

Props:

PropTypeDefaultDescription
initialEmailstring''Pre-filled email address
onBack() => void(required)Called when user clicks back
<script lang="ts">
import { PasskeyRequestSetupLink } from '@nebulr-group/bridge-svelte';
</script>
<PasskeyRequestSetupLink
initialEmail="user@example.com"
onBack={() => console.log('Back to login')}
/>

Lets authenticated users switch between workspaces. This is different from TenantSelector (which is part of the login flow). WorkspaceSelector is used in an already-authenticated context, for example in a settings page or sidebar.

Props:

PropTypeDefaultDescription
onSwitch() => voidCalled after workspace switch
onError(error: Error) => voidCalled on error
workspaceItemSnippet<[{ workspace, isActive, isLoading, onSelect }]>Custom render snippet for each workspace

Usage:

<script lang="ts">
import { WorkspaceSelector } from '@nebulr-group/bridge-svelte';
</script>
<WorkspaceSelector
onSwitch={() => window.location.reload()}
onError={(err) => console.error(err)}
/>

Custom workspace item:

<script lang="ts">
import { WorkspaceSelector, type Workspace } from '@nebulr-group/bridge-svelte';
</script>
<WorkspaceSelector onSwitch={() => window.location.reload()}>
{#snippet workspaceItem({ workspace, isActive, isLoading, onSelect })}
<button
class="workspace-item"
class:active={isActive}
disabled={isLoading}
onclick={onSelect}
>
{workspace.name}
{#if isActive}<span>(current)</span>{/if}
</button>
{/snippet}
</WorkspaceSelector>