Documentação

Autenticação multifator

Cadastrar um fator, o desafio que ele produz na entrada, e recuperação.

MFA tem duas metades que são fáceis de confundir. Cadastro é algo que um usuário autenticado faz na própria conta. O desafio é o que a entrada passa a produzir depois, para quem tem um fator.

Você constrói as duas como telas separadas, e só uma delas está no caminho da entrada.

O lado do desafio

Assim que um usuário tem um fator habilitado, o signIn para de devolver sessão direto:

const { data, error } = await nulvless.auth.signIn({ email, password });

if (error) return showError();
if (data.challenge) return goToVerify(data.challenge);

// sem desafio — autenticado

A plataforma escolhe com qual fator desafiar, numa ordem de preferência fixa:

totp1º

Um app autenticador. Escolhido sempre que o usuário tem um.

sms_otp2º

Um código por SMS.

email_otp3º

Um código por e-mail.

recovery_codesúltimo

Só quando nada mais está disponível.

challenge.preferred é o escolhido; challenge.factors lista as alternativas que o usuário de fato possui. É desse segundo campo que se constrói um link de "tentar de outra forma".

Trocando de método no meio do fluxo

Um usuário sem acesso ao autenticador não recomeça a entrada — o desafio já em andamento é movido para outro método:

await nulvless.auth.challenges.change({
  token: challenge.token,
  type: 'email_otp',
});

O campo é type, não method.

Finalizando

A mesma chamada de todos os outros desafios, e é por isso que um componente só atende a todos:

const { data } = await nulvless.auth.verifyChallenge({
  token: challenge.token,
  code,
});

Um código de recuperação vai no mesmo campo code — não existe chamada separada para ele.

O lado do cadastro

O cadastro tem dois passos de propósito: o segredo é entregue primeiro, e o fator só passa a existir quando o usuário prova que o dispositivo dele gera o código certo.

Inicie

const { data } = await nulvless.auth.factors.enroll({
  type: 'totp',
  name: 'Meu autenticador',
});

// data.secret → entrada manual
// data.url    → renderize como QR code
// data.token  → leve para o enable()
typeMFAMethodRequired
totp
sms_otp
email_otp
recovery_codes
namestringOptional

Aparece na lista de fatores do usuário.

noncestringOptional

Nonce de sudo — veja abaixo.

secret e url só voltam para TOTP. O url é uma URL de provisionamento, então você a transforma direto em QR code sem montar nada à mão.

Confirme

await nulvless.auth.factors.enable({
  token: data.token,
  code: primeiroCodigoDoApp,
});

Até isso funcionar, o fator não existe. Um cadastro abandonado deixa a conta exatamente como estava — não há estado meio-habilitado para limpar.

Listando o que um usuário tem

const { data: factors } = await nulvless.auth.factors.list();

Cada entrada traz id, type, name, status (configured ou not_configured) e created_at. O id é o que o unenroll recebe:

const totp = factors.find((factor) => factor.type === 'totp');
if (totp?.id) await nulvless.auth.factors.unenroll({ id: totp.id });

Um usuário pode ter vários apps autenticadores e fatores SMS — até 10 somados, limite que cada ambiente pode reduzir nas configurações de entrada do console (Fatores por usuário, na seção de MFA). Cada fator SMS tem seu próprio telefone, que pode ser diferente do telefone do usuário. E-mail e códigos de recuperação continuam um de cada. No login, um desafio totp aceita o código de qualquer app do usuário, e challenges.change({ token, type: 'sms_otp', factor_id }) escolhe qual telefone recebe o SMS.

Códigos de recuperação

const { data } = await nulvless.auth.factors.getRecoveryCodes();
// data.codes → string[]

await nulvless.auth.factors.deleteRecoveryCodes();

Mostre uma vez, no cadastro, e faça o usuário confirmar que guardou. São o único caminho de volta para quem perde o dispositivo — a alternativa é um fluxo de suporte em que um admin remove o fator com auth.admin.users.removeFactor, e a plataforma não questiona essa chamada.

Tudo que é destrutivo exige sudo

enroll, unenroll, getRecoveryCodes e deleteRecoveryCodes são todos protegidos por sudo. Só o enable não é — ele já está provando posse do dispositivo.

O auth.sessions.sudo() envia o código nos dois casos. Gaste ele na sessão com sudoVerify, ou só nesta chamada:

await nulvless.auth.factors.unenroll({ id, nonce: code });

O nonce é esse código. Vai nos params, não nas options.

É esse o motivo do sudo na gestão de MFA: uma sessão roubada não pode remover silenciosamente o segundo fator que protege a conta. Veja Modo sudo.

Quando o ambiente exige MFA

Um ambiente pode ser configurado para MFA não ser opcional. Duas configurações importam:

As duas ficam no console, em Autenticações.

Exigir autenticação de dois fatores (MFA)

Sessões que nunca usaram um segundo fator podem ser rejeitadas quando solicitado.

Métodos de autenticação

Quais tipos de fator este ambiente permite.

Exigir MFA não cadastra ninguém por você. O seu produto ainda precisa levar um usuário sem fator até o cadastro — normalmente verificando factors.list() após a entrada e roteando para uma tela de configuração quando vier vazio.

Próximos

Nesta página