auth.challenges
O passo entre "credenciais aceitas" e "sessão emitida".
Um desafio é o que volta quando a plataforma precisa de mais uma coisa antes de
emitir uma sessão — um OTP, um código TOTP, uma confirmação de sudo. Você
raramente cria um: signIn, signUp e sessions.sudo() devolvem um quando o
ambiente ou os fatores da pessoa exigem.
Todo desafio é de vida curta, tem um propósito só, e é limitado a quatro
tentativas. No quarto código errado o desafio é fechado no servidor e novas
verificações falham com action_verification_failed — a pessoa precisa começar o
fluxo de novo em vez de continuar chutando.
O formato
tokenstring | nullRequiredDe vida curta, e a única alça que você tem sobre o desafio. Leve-o até o
verify(). Ele não é o token de sessão e não pode ser usado como tal. É null
quando preferred é link: não há código para verificar.
preferredChallengeSubjectRequiredQual método apresentar primeiro. A plataforma escolhe — você renderiza.
totpUm app autenticador. Escolhido sempre que a pessoa tem um.sms_otpUm código enviado por SMS.email_otpUm código enviado por e-mail.recovery_codesUm dos códigos salvos no cadastro do fator.linkUm link foi enviado por e-mail. Não há nada a coletar — mostre "confira seu e-mail" em vez de um campo de código.
factorsAuthMethod[]OptionalAs alternativas que esta pessoa de fato possui. É com isso que se monta um link de "tentar de outro jeito" — não ofereça métodos que ela não cadastrou.
identifierstringOptionalJá vem mascarado pela plataforma — u***@example.com. Pode ser renderizado
como está, e é por isso que você não precisa do endereço real na tela de
verificação.
subjectChallengeSubjectRequiredPor que o desafio foi emitido.
sign_upConfirmando uma conta nova.verify_pending_userConfirmando uma conta que nunca foi ativada.sign_in_with_otpEntrada sem senha.mfaUm segundo fator depois da senha.sudoReconfirmando identidade para uma ação sensível.add_emailAdicionando uma identidade de e-mail a uma conta existente.add_phoneAdicionando uma identidade de telefone.expires_innumberOptionalMilissegundos até o desafio expirar. Configurado por ambiente e por método, então não cravar uma contagem regressiva no código.
O subject é o que permite uma tela só atender todos os fluxos: o mesmo campo de
código pode finalizar um cadastro, um passo de MFA ou uma elevação de sudo, e o
subject diz qual texto mostrar e para onde mandar a pessoa depois.
verify
verify({ token, code }) → Authenticated
Envia o código que a pessoa digitou. É a chamada que encerra o fluxo.
const { data, error } = await nulvless.auth.challenges.verify({
token: challenge.token,
code,
});tokenstringRequiredVindo do objeto Challenge.
codestringRequiredO que quer que a pessoa tenha digitado. Um código de recuperação vai neste mesmo campo — não há chamada separada para ele, e a plataforma decide contra qual fator verificar a partir do próprio desafio.
Devolve Authenticated — o mesmo formato que o signIn devolve. Em caso de
sucesso o SDK guarda a sessão automaticamente, então um verify bem-sucedido
deixa o client autenticado sem trabalho adicional.
Um código errado conta no orçamento de quatro tentativas. A quarta falha fecha o
desafio, e o erro muda de uma rejeição comum para action_verification_failed.
Distinga os dois na sua interface: o primeiro diz para a pessoa redigitar, o
último diz para recomeçar.
O auth.verifyChallenge é a mesma operação no client de topo. Use o que ler
melhor onde você está.
get
get(token) → Challenge
Lê o estado atual de um desafio sem consumir uma tentativa.
const { data: challenge } = await nulvless.auth.challenges.get(token);tokenstringRequiredO token do desafio.
O caso para o qual isso existe: a pessoa recarrega a página de verificação, ou abre o link do e-mail em outro dispositivo. Você tem o token na URL mas nenhuma ideia se o desafio ainda está aberto. Ler antes permite mostrar um estado de expirado em vez de coletar um código que não pode funcionar.
change
change({ token, type, factor_id }) → { challenge }
Troca um desafio já em andamento para outro método, sem reiniciar a entrada.
const { data } = await nulvless.auth.challenges.change({
token: challenge.token,
type: 'email_otp',
});
// O novo desafio substitui o antigo.
setChallenge(data.challenge);tokenstringRequiredDo desafio atualmente em andamento.
typeMFAMethodRequiredO método para o qual mudar. Repare que o campo é type, não method.
totpNada é enviado — o app já gera os códigos.sms_otpEnvia um código novo por SMS.email_otpEnvia um código novo por e-mail.recovery_codesNada é enviado.factor_idstringOptionalSó para sms_otp. Qual fator SMS recebe o código — um id de
challenge.factors. Por padrão, o fator SMS mais antigo.
É isso que sustenta o "tentar de outro jeito" — quem está sem o autenticador migra para um código por e-mail sem voltar à tela de senha.
Duas coisas a tratar:
- Volta um desafio novo, com um token novo. Substitua o que você
segurava, ou o próximo
verifyvai falhar contra um desafio que não vale mais. - Mudar para um método de OTP envia mensagem, então entra no mesmo limite de
taxa de qualquer outro envio de verificação. Mudar para
totpourecovery_codesnão envia nada e não é limitado.
Ofereça apenas os métodos listados em challenge.factors.
create
create({ type, factor_id }) → { challenge }
Inicia um desafio diretamente, para um método que você nomeia.
const { data } = await nulvless.auth.challenges.create({ type: 'totp' });typeMFAMethodRequiredCom qual método desafiar.
factor_idstringOptionalSó para sms_otp. Qual fator SMS recebe o código — um id de
factors.list(). Por padrão, o fator SMS mais antigo.
Um desafio totp aceita o código de qualquer app autenticador do usuário.
Para sms_otp, passe factor_id para escolher o telefone:
const { data } = await nulvless.auth.challenges.create({ type: 'sms_otp', factor_id });Diferente do resto deste namespace, o create exige uma sessão
autenticada — ele desafia o usuário atual. Não faz parte da entrada, onde
ainda não existe sessão.
Você precisa disso bem menos do que parece. Entrada, cadastro e sudo já
devolvem um desafio; recorra ao create quando o seu próprio produto quiser
reconfirmar alguém no meio da sessão por um motivo que a plataforma desconhece.
O link não é válido aqui — ele é um valor de preferred que a plataforma
escolhe durante o cadastro, não um método que você possa pedir.