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, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { AUTH0_AUTH, Auth0AuthProvider, provideAuth0Auth, provideConvex } from 'convex-angular';
import { Observable, firstValueFrom } from 'rxjs';
/**
* These docs do not depend on `@auth0/auth0-angular`, so the slice of its
* `AuthService` used below is declared locally, with the same shape: Observables,
* not Signals or Promises. In your application you inject the real `AuthService`
* (`inject(AuthService)`) instead of this token.
*/
interface Auth0Sdk {
isLoading$: Observable<boolean>;
isAuthenticated$: Observable<boolean>;
getAccessTokenSilently(options: {
detailedResponse: true;
cacheMode?: 'on' | 'off';
}): Observable<{ id_token: string } | undefined>;
}
const AUTH0_SDK = new InjectionToken<Auth0Sdk>('AUTH0_SDK');
@Injectable({ providedIn: 'root' })
export class Auth0AuthService implements Auth0AuthProvider {
private readonly auth0 = inject(AUTH0_SDK);
readonly isLoading = toSignal(this.auth0.isLoading$, { initialValue: true });
readonly isAuthenticated = toSignal(this.auth0.isAuthenticated$, { initialValue: false });
// Convex validates the OIDC **id token**, not the access token, so ask for
// the detailed response and return `id_token`. The SDK call returns an
// Observable, so unwrap it with `firstValueFrom` before reading the token.
async getAccessTokenSilently(options?: { cacheMode?: 'on' | 'off' }): Promise<string | null> {
const response = await firstValueFrom(
this.auth0.getAccessTokenSilently({
detailedResponse: true,
cacheMode: options?.cacheMode,
}),
);
return response?.id_token ?? null;
}
}
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 your application, inject(AuthService) from @auth0/auth0-angular replaces inject(AUTH0_SDK); bridge its Observables with toSignal(...) and firstValueFrom(...) as above, because awaiting an Observable directly yields the Observable itself, not the token.

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