Angular
Signals for the session, a guard for routes, an interceptor for your API.
A browser-side setup: one injectable service holds the client, signals expose the session to templates, and an interceptor puts the token on requests to your own backend.
Install
npm install @nulvless/nulvless-jsAn injectable service
import { Injectable, signal } from '@angular/core';
import { createClient, Storage, type Session } from '@nulvless/nulvless-js';
import { environment } from '../environments/environment';
@Injectable({ providedIn: 'root' })
export class NulvlessService {
readonly client = createClient(environment.nulvlessUrl, environment.nulvlessKey, {
storage: Storage.Local,
trackUser: true,
});
readonly session = signal<Session | null>(null);
readonly loading = signal(true);
constructor() {
this.client.auth.getSession().then(({ data }) => {
this.session.set(data);
this.loading.set(false);
});
this.client.auth.on('signed-in', async () => {
const { data } = await this.client.auth.getSession();
this.session.set(data);
});
this.client.auth.on('signed-out', () => this.session.set(null));
this.client.auth.on('unauthorized', () => this.session.set(null));
}
signIn(email: string, password: string) {
return this.client.auth.signIn({ email, password });
}
signOut() {
return this.client.auth.signOut();
}
}Exposing the session as a signal means templates re-render on their own when authentication changes anywhere.
Sign in
import { Component, inject, signal } from '@angular/core';
import { Router } from '@angular/router';
import { NulvlessService } from './nulvless.service';
@Component({
selector: 'app-sign-in',
standalone: true,
template: `
<form (submit)="submit($event)">
<input #email type="email" />
<input #password type="password" />
@if (error()) { <p role="alert">{{ error() }}</p> }
<button type="submit">Sign in</button>
</form>
`,
})
export class SignInComponent {
private nulvless = inject(NulvlessService);
private router = inject(Router);
error = signal<string | null>(null);
async submit(event: Event) {
event.preventDefault();
const form = event.target as HTMLFormElement;
const { data, error } = await this.nulvless.signIn(
form.email.value,
form.password.value,
);
if (error) return this.error.set('Check your email and password.');
if (data.challenge) return this.router.navigate(['/verify']);
this.router.navigate(['/']);
}
}Guard a route
import { inject } from '@angular/core';
import { Router, type CanActivateFn } from '@angular/router';
import { NulvlessService } from './nulvless.service';
export const authGuard: CanActivateFn = async (route) => {
const nulvless = inject(NulvlessService);
const router = inject(Router);
const { data } = await nulvless.client.auth.getSession();
return data ? true : router.createUrlTree(['/sign-in']);
};For a permission rather than just a session:
const { error } = await nulvless.client.auth.verify({
resource: route.params['org'],
roles: ['owner', 'admin'],
});
return error ? router.createUrlTree(['/forbidden']) : true;Attach the token to your own API
import { inject } from '@angular/core';
import type { HttpInterceptorFn } from '@angular/common/http';
import { from, switchMap } from 'rxjs';
import { NulvlessService } from './nulvless.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const nulvless = inject(NulvlessService);
return from(nulvless.client.auth.getAccessToken()).pipe(
switchMap((token) =>
next(token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req),
),
);
};Ask for the token per request rather than caching it — getAccessToken
refreshes when it is close to expiring.