Skip to content

Auth0

convex-angular has no dependency on @auth0/auth0-angular. You write a small service that exposes Auth0’s state as signals, register it under the AUTH0_AUTH token, and provideAuth0Auth() bridges it to Convex.

interface Auth0AuthProvider {
isLoading: Signal<boolean>;
isAuthenticated: Signal<boolean>;
getAccessTokenSilently(options?: { cacheMode?: 'on' | 'off' }): Promise<string | null>;
error?: Signal<Error | undefined>;
}
Member Type Required Purpose
isLoading Signal<boolean> yes true while Auth0 initializes, false once auth state is known. Forwarded unchanged.
isAuthenticated Signal<boolean> yes Whether Auth0 reports the user as authenticated. Forwarded unchanged.
getAccessTokenSilently (options?: { cacheMode?: 'on' | 'off' }) => Promise<string | null> yes Returns the token for Convex, or null when signed out.
error Signal<Error | undefined> no Provider-owned errors, mirrored onto injectAuth().error().
const AUTH0_AUTH: InjectionToken<Auth0AuthProvider>;

Register your implementation under this token before calling provideAuth0Auth(). Prefer useExisting over useClass when the service is also injected elsewhere, so Angular reuses the singleton instead of constructing a second instance.

function provideAuth0Auth(): EnvironmentProviders;

It registers a CONVEX_AUTH factory that adapts AUTH0_AUTH, and then provideConvexAuth().

forceRefreshToken maps to Auth0’s cacheMode:

Convex asks for Call made
forceRefreshToken: false getAccessTokenSilently({ cacheMode: 'on' })
forceRefreshToken: true getAccessTokenSilently({ cacheMode: 'off' })

Unlike the Clerk adapter, there is no reauthVersion. Auth0 has no equivalent of a session or organization identity that the adapter can watch, so auth setup re-runs only when isLoading or isAuthenticated change. If your application needs to force a re-auth on some other signal, implement a custom provider and expose your own reauthVersion.

auth0-auth.service.ts + app.config.ts
import { ApplicationConfig, Injectable, InjectionToken, Signal, inject } from '@angular/core';
import { AUTH0_AUTH, Auth0AuthProvider, provideAuth0Auth, provideConvex } from 'convex-angular';
/**
* These docs do not depend on `@auth0/auth0-angular`, so the slice of its
* `AuthService` used below is declared locally. In your application you inject
* the real `AuthService` and bridge its observables with `toSignal(...)`.
*/
interface Auth0Sdk {
isLoading: Signal<boolean>;
isAuthenticated: Signal<boolean>;
getAccessTokenSilently(options: { detailedResponse: true; cacheMode?: 'on' | 'off' }): Promise<{ id_token: string }>;
}
const AUTH0_SDK = new InjectionToken<Auth0Sdk>('AUTH0_SDK');
@Injectable({ providedIn: 'root' })
export class Auth0AuthService implements Auth0AuthProvider {
private readonly auth0 = inject(AUTH0_SDK);
readonly isLoading = this.auth0.isLoading;
readonly isAuthenticated = this.auth0.isAuthenticated;
// Convex validates the OIDC **id token**, not the access token, so ask for
// the detailed response and return `id_token`.
async getAccessTokenSilently(options?: { cacheMode?: 'on' | 'off' }): Promise<string | null> {
const response = await this.auth0.getAccessTokenSilently({
detailedResponse: true,
cacheMode: options?.cacheMode,
});
return response.id_token;
}
}
export const appConfig: ApplicationConfig = {
providers: [
provideConvex('https://example-123.convex.cloud'),
{ provide: AUTH0_AUTH, useExisting: Auth0AuthService },
// Already includes provideConvexAuth(). Do not register that as well.
provideAuth0Auth(),
],
};

In a real application the SDK binding comes from Auth0 directly:

import { toSignal } from '@angular/core/rxjs-interop';
import { AuthService } from '@auth0/auth0-angular';

Then read the state with injectAuth(), gate templates with the auth directives, and protect routes with the route guards.