Skip to content

How @nebulr-group/bridge-nextjs represents auth state, exposes hooks, and protects routes.

<BridgeProvider> initializes a singleton BridgeAuth (from @nebulr-group/bridge-auth-core) on mount. Auth-core emits events:

  • auth:login — when tokens are issued.
  • auth:logout — when tokens are cleared.
  • auth:token-refreshed — when refresh succeeds.
  • auth:state-change — when the state machine transitions (unauthenticatedmfa-requiredtenant-selectionauthenticated).
  • auth:profile — when the profile loads or updates.
  • auth:workspace-changed — when the user switches workspaces.
  • auth:error — on any auth error.

These events update a Zustand store (useBridgeStore) which all the hooks read from.

const { isAuthenticated, isLoading, error, authState, login, logout, handleCallback } = useAuth();
  • isAuthenticated — boolean, derived from token presence.
  • isLoadingtrue until bootstrap completes.
  • authState'unauthenticated' | 'authenticating' | 'mfa-required' | 'mfa-setup-required' | 'tenant-selection' | 'authenticated'.
  • login() — redirects to hosted bridge auth.
  • logout() — clears tokens and resets state.
const { profile, isLoading, error, updateProfile, isOnboarded, hasMultiTenantAccess } = useProfile();

profile is the auth-core Profile type — has fullName, email, username, onboarded, multiTenantAccess, etc.

  • useAuthState() — just the state machine value.
  • useBridgeTokens()TokenSet | null.
  • useIsOnboarded() — boolean.
  • useHasMultiTenantAccess() — boolean.
  • useTenantUsers()TenantUser[] (populated during tenant-selection).
  • useBridgeReady() — boolean, true once bootstrap finishes.
middleware.ts
import { withBridgeAuth } from '@nebulr-group/bridge-nextjs/server';
export default withBridgeAuth({
rules: [
{ match: '/', public: true },
{ match: '/auth/:path*', public: true },
// everything else requires authentication
],
});
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

When middleware blocks a request, it redirects to the configured loginRoute (default /auth/login).

'use client';
import { ProtectedRoute } from '@nebulr-group/bridge-nextjs/client';
export default function DashboardPage() {
return (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
);
}
'use client';
import { useProfile } from '@nebulr-group/bridge-nextjs/client';
export function ProfileBadge() {
const { profile } = useProfile();
return <span>{profile?.fullName ?? profile?.email}</span>;
}

For a simple display name, use <ProfileName />:

import { ProfileName } from '@nebulr-group/bridge-nextjs/client';
<ProfileName className="text-gray-700" />
'use client';
import { getBridgeAuth } from '@nebulr-group/bridge-nextjs/client';
export async function fetchUser() {
const token = await getBridgeAuth().getAccessToken();
const res = await fetch('/api/me', {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
}

logout() clears localStorage + cookies, fires auth:logout, and resets all hooks to unauthenticated state. There’s no redirect by default — the consumer can navigate after:

const router = useRouter();
const { logout } = useAuth();
await logout();
router.push('/');
  • getBridgeAuth() throws if called before <BridgeProvider> has mounted. Always call it from 'use client' components after mount.
  • OAuth callback preserves ?payment=* by default. If you add other query params your app needs after login, pass them to createBridgeCallbackRoute({ preserveQueryParams: ['payment', 'ref'] }).
  • MFA-setup-required state is reachable only when the Bridge app forces MFA for new users. <LoginForm> handles it; if you wire a custom login UI, check authState === 'mfa-setup-required' and render <MfaSetup />.