Route guards
The router guards wait for auth to settle and then decide once. All three are
typed CanActivateFn & CanMatchFn, so the same value works in either slot.
convexAuthGuard
Section titled “convexAuthGuard”const convexAuthGuard: CanActivateFn & CanMatchFn;Requires authentication. It:
- Waits until
auth.status()is no longer'loading'. - Allows navigation when the status is
'authenticated'or'refreshing'. - Otherwise redirects to the login route with a
returnUrlquery parameter.
'refreshing' counts as authenticated on purpose. It only ever occurs while the user is still
authenticated, so bouncing to login would visibly sign the user out for a routine recovery.
convexAuthGuard is exactly createConvexAuthGuard() with no options.
convexUnauthGuard
Section titled “convexUnauthGuard”const convexUnauthGuard: CanActivateFn & CanMatchFn;For routes that only make sense signed out — login, registration, password reset. It waits for
'loading' to clear, allows the navigation when the status is 'unauthenticated', and otherwise
redirects to authenticatedRoute (default '/'). Note the asymmetry: because only
'unauthenticated' is allowed, a user in the 'refreshing' state is redirected away, same as a
fully authenticated one.
There is no returnUrl on this redirect.
createConvexAuthGuard()
Section titled “createConvexAuthGuard()”function createConvexAuthGuard(options?: ConvexAuthGuardOptions): CanActivateFn & CanMatchFn;
interface ConvexAuthGuardOptions { allow?: (auth: { token: string; claims: Record<string, unknown> }) => boolean; forbiddenRoute?: string; loginRoute?: string;}| Option | Type | Behavior |
|---|---|---|
allow |
(auth: { token: string; claims: Record<string, unknown> }) => boolean |
Extra check run after authentication is confirmed, with the current JWT and its decoded claims from injectAuth().getAuth(). Return true to allow navigation. |
forbiddenRoute |
string |
Where authenticated users who fail allow are sent. When omitted, the guard returns false and navigation is blocked with no redirect. |
loginRoute |
string |
Per-guard override of the login route. Resolution order: options.loginRoute → CONVEX_AUTH_GUARD_CONFIG.loginRoute → '/login'. |
Claim-gated guards wait through 'refreshing'
Section titled “Claim-gated guards wait through 'refreshing'”When allow is set, the guard’s settle condition tightens: it waits until the status is neither
'loading' nor 'refreshing'. A 'refreshing' status means the server rejected the current
token, so its claims are exactly the ones you must not trust. Waiting for the replacement
guarantees allow never reads stale claims.
Guards without allow do not wait for a refresh — there are no claims to read, and the user is
still authenticated.
A missing snapshot blocks
Section titled “A missing snapshot blocks”allow runs against injectAuth().getAuth(). If that snapshot is undefined — no token is set
on the Convex client — the guard treats the check as failed even though the status is
'authenticated', and redirects to forbiddenRoute or blocks. There is no “allow by default”
path when claims cannot be read.
CONVEX_AUTH_GUARD_CONFIG
Section titled “CONVEX_AUTH_GUARD_CONFIG”const CONVEX_AUTH_GUARD_CONFIG: InjectionToken<ConvexAuthGuardConfig>;
interface ConvexAuthGuardConfig { loginRoute?: string; authenticatedRoute?: string;}| Option | Default | Used by |
|---|---|---|
loginRoute |
'/login' |
convexAuthGuard and createConvexAuthGuard() when redirecting unauthenticated users. Overridden by ConvexAuthGuardOptions.loginRoute. |
authenticatedRoute |
'/' |
convexUnauthGuard when redirecting already-authenticated users. |
The token is optional; omit it entirely to take both defaults.
returnUrl
Section titled “returnUrl”When an unauthenticated user is redirected, the guard parses loginRoute into a UrlTree,
preserves any query parameters already written into that route, and adds returnUrl pointing at
the navigation target.
The captured target is the URL of the in-flight navigation: the final URL after redirects when the
router has resolved one (which is what canActivate sees, matching RouterStateSnapshot.url), and
otherwise the requested URL — canMatch runs during recognition, before redirects resolve. Path,
query parameters, and fragment are all preserved. Read it in your login component and navigate back
after a successful sign-in.
Prefer canMatch for lazy routes
Section titled “Prefer canMatch for lazy routes”Both slots work, but they differ in what they prevent:
| Slot | Effect on failure |
|---|---|
canMatch |
The route does not match at all. loadComponent / loadChildren is never invoked, so the protected chunk is never downloaded. |
canActivate |
The route matches and its bundle is fetched first; only then is activation blocked. |
Use canMatch for anything lazily loaded. canActivate remains appropriate for eagerly declared
routes and for child routes where you want matching to succeed.
Example
Section titled “Example”import { ApplicationConfig } from '@angular/core';import { Routes } from '@angular/router';import { CONVEX_AUTH_GUARD_CONFIG, ConvexAuthGuardConfig, convexAuthGuard, convexUnauthGuard, createConvexAuthGuard, provideConvex, provideConvexAuthFromExisting,} from 'convex-angular';
import { MyAuthService } from './auth-custom-provider';
// Claim-gated guard: `allow` runs only after the token has settled, so the// claims are never read from a token the server already rejected.const adminGuard = createConvexAuthGuard({ allow: ({ claims }) => claims['role'] === 'admin', forbiddenRoute: '/forbidden',});
export const routes: Routes = [ { path: 'login', loadComponent: () => import('./auth-directives').then((m) => m.ShellComponent), // Signed-in users are bounced to `authenticatedRoute` (default '/'). canActivate: [convexUnauthGuard], }, { path: 'dashboard', // Prefer canMatch for lazy routes: a failed check stops the route from // matching at all, so the protected bundle is never downloaded. canMatch: [convexAuthGuard], loadComponent: () => import('./auth-directives').then((m) => m.ShellComponent), }, { path: 'admin', canMatch: [adminGuard], loadComponent: () => import('./auth-directives').then((m) => m.ShellComponent), },];
const guardConfig: ConvexAuthGuardConfig = { loginRoute: '/auth/signin', authenticatedRoute: '/dashboard',};
export const appConfig: ApplicationConfig = { providers: [ provideConvex('https://example-123.convex.cloud'), provideConvexAuthFromExisting(MyAuthService), { provide: CONVEX_AUTH_GUARD_CONFIG, useValue: guardConfig }, ],};