Documentação

Cadastro e verificação

Por que o cadastro sempre devolve um desafio, e a única ramificação que atende todos os modos.

O signUp nunca autentica ninguém sozinho. Ele devolve um desafio — o endereço ou telefone precisa ser confirmado antes — e a cara desse desafio depende de como o ambiente está configurado.

O ponto desta página é que você escreve a ramificação uma vez e nunca mais mexe nela quando essa configuração mudar.

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

if (error) return;

if (data.challenge?.preferred === 'link') {
  // Nada a coletar — um link foi enviado por e-mail.
}

if (data.challenge) {
  // Um código foi enviado. Colete.
}

O que decide qual é

Uma configuração do ambiente, Inscrições → Tipo de confirmação:

otpUm código é enviado. A conta é criada quando o código é verificado.
linkUm link é enviado por e-mail. Exige um endereço de e-mail.

Uma segunda configuração, Autenticar após inscrição, decide se a conta — uma vez confirmada — também produz uma sessão.

confirmationType: 'otp'

O signUp devolve um desafio com subject: 'sign_up' e preferred como email_otp quando o usuário tem e-mail, sms_otp caso contrário.

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

if (data.challenge) {
  // Colete o código, e então:
  const { data: done } = await nulvless.auth.verifyChallenge({
    token: data.challenge.token,
    code,
  });
}

A conta ainda não existe

Neste caminho o usuário não é criado pelo signUp. Os dados enviados ficam carregados no desafio e a conta é criada quando o código é verificado.

Duas consequências que valem ser consideradas no design: um cadastro abandonado não deixa nada para trás, e o e-mail não fica reservado até a confirmação — então duas pessoas podem começar um cadastro com o mesmo endereço, e a segunda a confirmar vai falhar.

Depois de um verifyChallenge bem-sucedido, o authenticateAfterRegistering vale de novo: você recebe uma sessão, ou só o usuário.

O desafio volta com preferred: 'link', o que significa que não há nada a coletar. Mostre uma tela de "confira seu e-mail" em vez de um campo de código:

if (data.challenge?.preferred === 'link') {
  setCheckYourEmail(true);
  return;
}

O link cai na URL configurada como confirmAccountLinkUri, carregando um token. A sua página finaliza:

const token = new URLSearchParams(location.search).get('token');
await nulvless.auth.activate(token);

O activate não devolve sessão. Confirmar por link prova o endereço; não autentica a pessoa. Mande ela para a tela de entrada depois.

Este caminho exige e-mail — um ambiente em link com cadastro só por telefone é rejeitado.

Um componente para todo código

Como o desafio carrega o subject, a mesma tela serve cadastro, entrada por OTP, MFA e sudo:

function VerifyCode({ challenge }: { challenge: Challenge }) {
  return (
    <form onSubmit={submit}>
      <p>Enviamos um código para {challenge.identifier}</p>
      <input value={code} onChange={(e) => setCode(e.target.value)} inputMode="numeric" />
    </form>
  );
}

challenge.identifier chega mascarado — u***@example.com — então pode ser exibido sem risco. O expires_in está lá para uma contagem regressiva.

O que pode rejeitar um cadastro

Verificado contra as checagens da própria plataforma, na ordem em que rodam:

Cadastro fechado403

Permitir novos usuários a se inscreverem está desligado neste ambiente. Traz mensagem, sem code.

Captcha400

captcha_required quando o token falta, captcha_verification_failed quando o provedor rejeita. Habilitado por ambiente para cadastros.

Sem e-mail nem telefone400

Um dos dois é sempre obrigatório. Só mensagem, sem code.

Credencial obrigatória ausente400

Credenciais obrigatórias pode exigir e-mail, telefone ou ambos. Só mensagem — veja a nota abaixo.

Domínio de e-mail bloqueado403

O endereço não passou na lista permitida do ambiente. Só mensagem, sem code.

Senha rejeitada400

password_weak para tamanho e requisitos de caracteres; password_leaked quando a checagem de vazamento está ligada e a senha é conhecida. São dois códigos diferentes, não um.

Já existe422

email_already_exists, phone_already_exists ou username_already_exists — mas só quando exatamente um campo colide. Quando dois colidem de uma vez, a resposta traz a mensagem e nenhum code.

"Credencial obrigatória ausente" calcula user_missing_email e user_missing_phone internamente e depois lança sem eles, então nenhum dos dois códigos chega até você. Trate pelo 400 e pelo seu próprio conhecimento do Credenciais obrigatórias, em vez de esperar um código que não é enviado.

username sozinho não basta

O SignUpParams aceita username, mas o cadastro sempre exige também um email ou um phone. Um cadastro só com username é rejeitado.

Cadastros que enviam algo — os caminhos otp e link — também têm limite de taxa, no mesmo balde dos outros envios de verificação.

As regras de senha são do ambiente, não suas

A senha é verificada contra as regras de Senhas: um tamanho mínimo, requisitos de caracteres e, opcionalmente, uma checagem contra senhas sabidamente vazadas. Não reimplemente essas regras no seu formulário — espelhe no texto de ajuda se quiser, mas deixe a plataforma ser quem rejeita, para os dois nunca discordarem.

Escrevendo a ramificação uma vez

O objetivo é justamente que este código sobreviva a um ambiente ser reconfigurado de otp para link e de volta:

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

if (error) return showError(error);

if (data.challenge?.preferred === 'link') return setCheckYourEmail(true);
if (data.challenge) return router.push('/verify', { state: { challenge: data.challenge } });

return router.push('/sign-in');

Três linhas, e nenhuma delas menciona em qual modo o ambiente está.

Próximos

Nesta página