React
Uma sessão que sobrevive ao reload e rotas que redirecionam quando ela acaba.
Tudo aqui roda no navegador, com a chave publicável. O fluxo completo: um client, um formulário de entrada que lida com MFA, um hook que acompanha a sessão e uma rota protegida.
Para verificações de permissão que o usuário não possa burlar, combine com Node na sua própria API.
Instalação
npm install @nulvless/nulvless-jsCrie o client
Um módulo só, importado em todo lugar. A chave publicável pode ficar no bundle.
import { createClient, Storage } from '@nulvless/nulvless-js';
export const nulvless = createClient(
import.meta.env.VITE_NULVLESS_URL,
import.meta.env.VITE_NULVLESS_KEY,
{ storage: Storage.Local, trackUser: true },
);Storage.Local mantém as pessoas autenticadas entre visitas. trackUser faz a
plataforma registrar o dispositivo e a localização em cada sessão.
Acompanhe a sessão
O SDK emite eventos quando a autenticação muda em qualquer ponto da aplicação, então um hook basta.
import { useEffect, useState } from 'react';
import type { Session } from '@nulvless/nulvless-js';
import { nulvless } from './nulvless';
export function useSession() {
const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
nulvless.auth.getSession().then(({ data }) => {
setSession(data);
setLoading(false);
});
const offIn = nulvless.auth.on('signed-in', () => {
nulvless.auth.getSession().then(({ data }) => setSession(data));
});
const offOut = nulvless.auth.on('signed-out', () => setSession(null));
const offDead = nulvless.auth.on('unauthorized', () => setSession(null));
return () => {
offIn();
offOut();
offDead();
};
}, []);
return { session, loading };
}on devolve a própria função de cancelamento — retorne-as do effect e o React
faz a limpeza ao desmontar.
Entrada
O formulário precisa lidar com dois desfechos: uma sessão, ou um desafio quando a pessoa tem MFA cadastrado.
import { useState } from 'react';
import type { Challenge } from '@nulvless/nulvless-js';
import { nulvless } from './lib/nulvless';
export function SignIn() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [challenge, setChallenge] = useState<Challenge | null>(null);
const [error, setError] = useState<string | null>(null);
async function submit(event: React.FormEvent) {
event.preventDefault();
setError(null);
const { data, error } = await nulvless.auth.signIn({ email, password });
if (error) return setError('Confira o e-mail e a senha.');
if (data.challenge) return setChallenge(data.challenge);
// Autenticado — a sessão foi guardada e o evento disparou.
}
if (challenge) return <VerifyCode challenge={challenge} />;
return (
<form onSubmit={submit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} type="email" />
<input value={password} onChange={(e) => setPassword(e.target.value)} type="password" />
{error && <p role="alert">{error}</p>}
<button type="submit">Entrar</button>
</form>
);
}Colete o código
Um componente serve a todos os desafios — MFA, entrada por OTP, sudo — porque
challenge.subject diz em qual fluxo você está.
import { useState } from 'react';
import type { Challenge } from '@nulvless/nulvless-js';
import { nulvless } from './lib/nulvless';
export function VerifyCode({ challenge }: { challenge: Challenge }) {
const [code, setCode] = useState('');
const [error, setError] = useState<string | null>(null);
async function submit(event: React.FormEvent) {
event.preventDefault();
const { error } = await nulvless.auth.verifyChallenge({
token: challenge.token,
code,
});
if (error) setError('Esse código não funcionou.');
}
return (
<form onSubmit={submit}>
<p>Enviamos um código para {challenge.identifier}</p>
<input value={code} onChange={(e) => setCode(e.target.value)} inputMode="numeric" />
{error && <p role="alert">{error}</p>}
<button type="submit">Confirmar</button>
</form>
);
}challenge.identifier já vem mascarado — u***@example.com — então pode ser
exibido sem risco.
Proteja uma rota
import { useSession } from './lib/use-session';
import { SignIn } from './SignIn';
export function Protected({ children }: { children: React.ReactNode }) {
const { session, loading } = useSession();
if (loading) return null;
if (!session) return <SignIn />;
return children;
}Saída
<button onClick={() => nulvless.auth.signOut()}>Sair</button>Passe 'others' para encerrar todas as outras sessões e manter esta, ou
'global' para encerrar todas.
Login social
<button onClick={() => nulvless.auth.authorize('google', {
redirect_uri: `${window.location.origin}/callback`,
})}>
Continuar com Google
</button>O SDK cuida do redirecionamento. Na rota de callback:
useEffect(() => {
const code = new URLSearchParams(window.location.search).get('code');
if (code) nulvless.auth.exchangeCodeForToken(code);
}, []);Renderize os botões a partir de getMethods() em
vez de fixar os provedores no código — assim, habilitar um no console não exige
novo deploy.