Autenticação em React: login com MFA em um app React
Um cliente, um hook de sessão, um formulário de login que lida com segundo fator e uma rota protegida, em cerca de cem linhas.

Nesta página
Este guia adiciona autenticação a um app React existente com
@nulvless/nulvless-js. No final você terá uma sessão que sobrevive a recargas,
um formulário de login que lida com autenticação multifator e uma rota que só
renderiza para usuários autenticados.
Tudo aqui roda no navegador com a chave publicável. Para checagens de permissão que o usuário não pode pular, combine com a sua própria API, como descrito no guia de Node.
Construa com seu agente
Um prompt pronto para Claude Code, Cursor, ChatGPT ou qualquer agente de código. Ele aponta o agente para este guia e para as docs, para que siga a API real em vez de adivinhar.
Antes de começar
Você precisa de um projeto no console e da URL do
emissor e da chave publicável do ambiente em que vai trabalhar. Use develop
enquanto desenvolve: é um armazenamento separado, então os usuários de teste
criados aqui nunca chegam à produção.
npm install @nulvless/nulvless-jsCrie um único cliente
Coloque o cliente em um módulo próprio e importe em todo lugar. A chave publicável é segura no bundle: ela só faz o que o usuário autenticado já poderia fazer.
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 conectadas entre visitas. trackUser permite
que a plataforma registre o dispositivo e a localização de cada sessão, que é o
que o usuário vê depois na lista de sessões ativas.
Acompanhe a sessão com um hook
O SDK emite eventos sempre que a autenticação muda em qualquer parte do app, então um único hook mantém o React sincronizado.
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, então retorná-las do efeito é
toda a limpeza necessária.
Faça login e espere um desafio
Um login pode terminar de duas formas: com uma sessão ou com um desafio, quando o usuário tem um segundo fator cadastrado. O formulário precisa lidar com as duas.
import { useState } from 'react';
import type { Challenge } from '@nulvless/nulvless-js';
import { nulvless } from './lib/nulvless';
import { VerifyCode } from './VerifyCode';
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 seu e-mail e sua senha.');
if (data.challenge) return setChallenge(data.challenge);
}
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>
);
}Quando não há desafio, não resta nada a fazer: a sessão já foi salva e o evento
signed-in já chegou ao seu hook.
Um componente para todo código
O mesmo componente coleta o código de MFA, de um login por código único ou de um pedido de sudo, porque o próprio desafio diz a qual fluxo pertence.
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á chega mascarado, como u***@example.com, então pode
ser exibido na tela.
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;
}Envolva qualquer parte do app em <Protected>. Quando a sessão termina, seja
porque o usuário saiu, porque expirou ou porque foi revogada de outro
dispositivo, o hook recebe o evento e a rota volta para o formulário de login.
Sair
<button onClick={() => nulvless.auth.signOut()}>Sair</button>Passe 'others' para encerrar todas as outras sessões e manter esta, ou
'global' para encerrar todas.
Próximos passos
- Adicione login social renderizando os provedores a partir de
getMethods(), assim ativar um no console não exige novo deploy. O guia de React tem a rota de callback. - Proteja as ações perigosas da sua tela de configurações com o modo sudo.
- Leia Por que usuários de staging nunca chegam à produção
antes de promover sua configuração para
main.
