Angular
Signals para a sessão, um guard para rotas, um interceptor para a sua API.
Uma configuração do lado do navegador: um serviço injetável guarda o client, signals expõem a sessão aos templates e um interceptor coloca o token nas requisições para o seu próprio backend.
Instalação
npm install @nulvless/nulvless-jsUm serviço injetável
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();
}
}Expor a sessão como signal faz os templates re-renderizarem sozinhos quando a autenticação muda em qualquer lugar.
Entrada
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">Entrar</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('Confira o e-mail e a senha.');
if (data.challenge) return this.router.navigate(['/verify']);
this.router.navigate(['/']);
}
}Proteja uma rota
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']);
};Para exigir uma permissão, e não apenas uma sessão:
const { error } = await nulvless.client.auth.verify({
resource: route.params['org'],
roles: ['owner', 'admin'],
});
return error ? router.createUrlTree(['/forbidden']) : true;Anexe o token à sua própria 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),
),
);
};Peça o token a cada requisição em vez de guardá-lo em cache — getAccessToken
renova quando ele está perto de expirar.