Verifica el correo y el teléfono
Cómo viajan los códigos de un solo uso, quién los envía y los campos que los piden.
Una persona demuestra que un correo o un número de teléfono es suyo escribiendo un código que el kit envió ahí. Esta guía cubre cuándo ocurre, qué necesita cada canal en .env, cómo es el código y cómo poner esos mismos campos verificados en tus propios formularios.
Cuándo se pide un código
| Momento | Canal | Condición |
|---|---|---|
| Registro, campo de correo | Correo | AUTH_ENABLE_VALIDATION=true. "Registrarse" rechaza el formulario hasta que la dirección queda confirmada. Apagado, el campo no tiene el enlace "Verificar ahora" y no se pide ningún código. |
| Registro, campo de teléfono | SMS o llamada | El mismo interruptor, con el mismo rechazo en "Registrarse". |
| Inicio de sesión de un paso con correo sin verificar | Correo | AUTH_APPROACH es CreationValidation o LoginValidation, y AUTH_ENABLE_VALIDATION=true. |
| Inicio de sesión de dos pasos, paso uno, correo sin verificar | Correo | Siempre. |
| Perfil, al cambiar el correo | Correo | AUTH_ENABLE_VALIDATION=true. Guardar rechaza la nueva dirección hasta que el código queda confirmado. |
| Perfil, al cambiar el teléfono | SMS o llamada | El mismo interruptor, con el mismo rechazo al guardar. |
En el perfil, a una identidad que nadie tocó nunca se le vuelve a pedir que se compruebe, así que guardar el nombre o la zona horaria no pide ningún código. Vaciar una identidad tampoco: se permite, y se lleva su verificación consigo, mientras otra identidad todavía pueda hacer entrar a la persona.
No se envía nada cuando la dirección o el número ya pertenece a otra cuenta: el campo muestra el error de unicidad en su lugar.
El código
- Los códigos por correo tienen diez caracteres, letras y dígitos, y distinguen mayúsculas. El asunto es "Validación de correo electrónico: " seguido del nombre de tu aplicación, y el cuerpo es un mensaje corto con el código.
- Los códigos por teléfono tienen cuatro dígitos. El mensaje dice "Tu app - Tu código de verificación es: 1234".
Cada solicitud genera un código nuevo, y cada reenvío reemplaza al anterior. El código vive en el estado de la pantalla que lo pidió: no tiene caducidad propia y desaparece con esa pantalla, así que recargar la página empieza de cero. Debe escribirse exacto; un valor equivocado responde "El código de verificación es incorrecto".
Bajo el campo del código corre una cuenta regresiva de treinta segundos: "¿No recibiste el código? 27s". Al terminar aparece el enlace "Reenviar código"; en la pantalla del teléfono, "Reenviar SMS" y "Recibir una llamada". La cuenta regresiva es el único freno: no hay límite de intentos contra un código.
Los códigos por correo y el mailer
Los códigos por correo pasan por el sistema de correo de Laravel, de forma síncrona, con el mailable App\Mail\Auth\ValidateEmail. El primer envío usa MAIL_MAILER. Cada reenvío avanza al siguiente mailer de mail_fallbacks en config/auth.php, que viene así:
'mail_fallbacks' => [
'mailgun',
],
Con la configuración por omisión una persona puede pedir el código dos veces: una por MAIL_MAILER, otra por Mailgun. Un tercer intento ya no encuentra mailer y muestra "No pudimos enviar el código de verificación. Por favor, intenta con otro correo electrónico". Agrega nombres al arreglo para permitir más reenvíos; el mismo nombre puede repetirse. El mailer que finalmente entregó queda registrado en el extra_data de la cuenta bajo mail_driver_used.
Los mailers y sus variables son los de config/mail.php: smtp, mailgun, ses, postmark, resend, log, y failover, que .env.example selecciona y que intenta Mailgun y luego SES. Sus credenciales y cómo elegir uno están en /help/email-and-sms-delivery.
Los códigos por teléfono y el proveedor
AUTH_VALIDATION_PROVIDER elige quién lleva el código al teléfono.
aws, el valor por omisión: Amazon SNS
El código sale como SMS transaccional por Amazon SNS con estas credenciales:
AUTH_VALIDATION_PROVIDER=aws
AWS_ACCESS_KEY_ID=your-aws-access-key-id
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key
AWS_DEFAULT_REGION=us-west-1
La región vuelve a us-west-1 cuando la variable falta. Son las mismas llaves que usan el mailer ses y el disco S3, así que un solo usuario IAM con permisos de SNS, SES y S3 cubre los tres. SNS sólo envía mensajes de texto: el diálogo del teléfono envía el SMS en cuanto abre y no hay opción de llamada. El número se normaliza a formato internacional antes de que abra el diálogo, así que un número local llega al proveedor con la forma que espera.
telnyx: Telnyx Verify
AUTH_VALIDATION_PROVIDER=telnyx
TELNYX_API_KEY=your-telnyx-api-key
TELNYX_VERIFY_PROFILE_ID=your-telnyx-verify-profile-id
TELNYX_TOKEN se acepta como nombre antiguo de la llave. Crea un perfil de Verify en el portal de Telnyx y pega su id; el kit le pasa su propio código de cuatro dígitos al perfil, así que el SMS y la llamada llevan el código que la pantalla espera. Con Telnyx el diálogo pregunta primero "Elige cómo quieres recibir el código de verificación" con dos botones, "Enviar SMS" y "Recibir una llamada", y la llamada sigue disponible bajo la cuenta regresiva como alternativa. El número se normaliza a formato internacional antes de la petición, y un número que no se puede normalizar se rechaza.
Con cualquiera de los dos proveedores, cuando falta una llave o el perfil, o el proveedor responde con error, la persona ve el diálogo "No se pudo enviar el código. No pudimos enviar el código de verificación a su teléfono. Por favor, inténtelo de nuevo." y el error se reporta a tu log. Dar de alta cada proveedor, y los SMS que el kit envía fuera de la verificación, se cubren en /help/email-and-sms-delivery.
Lo que ve la persona
En el registro y en el inicio de sesión la tarjeta completa se reemplaza: el título "Por favor confirma tu correo electrónico" o "Por favor confirma tu teléfono", una línea "Se envió un código a tu correo electrónico", un campo "Código de validación", un botón "Confirmar correo electrónico" o "Confirmar código" y la cuenta regresiva. Al confirmar el código, el flujo retoma donde se detuvo: la cuenta se crea, o la sesión abre.
En el perfil, y dentro del formulario de registro, la verificación abre como una ventana modal: "Verificar nuevo correo electrónico" o "Verificar nuevo teléfono", el mismo campo, botón y cuenta regresiva. Al confirmar, la ventana cierra y el campo muestra el nuevo valor con una insignia verde "Verificado".
En el perfil esa confirmación guarda la identidad que verificó, y sólo esa: el correo nuevo con su marca de tiempo, o el teléfono nuevo con la suya. Todo lo demás de la página espera a "Guardar" y pasa por la validación de siempre, así que un nombre a medio escribir o un campo que la persona seguía editando nunca lo escribe una verificación.

Los componentes <x-email-input> y <x-phone-input>
Ambos son componentes Blade que envuelven un campo con una insignia de verificación y el enlace "Verificar ahora", y ambos son los campos que el propio kit usa en el registro y en el perfil. Colócalos en cualquier formulario Livewire:
<x-email-input
:label="__('Email address')"
wire:model.live="user.email"
:validation-required="config('auth.enable_validation')"
:verified-at="$user->email === $user->getOriginal('email') ? $user->email_verified_at : null"
dispatch-context="profile"
/>
<x-phone-input
:label="__('Phone (Include country code)')"
wire:model.live="user.phone"
:validation-required="config('auth.enable_validation')"
:verified-at="same_phone_number($user->phone, $user->getOriginal('phone')) ? $user->phone_verified_at : null"
dispatch-context="profile"
/>
wire:model, o wire:model.live, es el único atributo obligatorio: nombra la propiedad de tu componente que guarda el valor. label y placeholder llegan al campo; cualquier otro atributo se acepta y se ignora. Los tres atributos opcionales son los mismos en los dos componentes:
| Atributo | Qué hace |
|---|---|
validation-required |
true muestra la fila de verificación: la insignia y el enlace "Verificar ahora". false deja un campo normal, sin nada que verificar. Por omisión es true en el correo y false en el teléfono, así que pasa config('auth.enable_validation') para que ambos sigan el interruptor del proyecto, que es lo que hacen las pantallas del propio kit. |
verified-at |
La marca de tiempo que decide si el valor en pantalla empieza como "Verificado". Pásala sólo mientras ese valor sea el que se verificó, y null en cualquier otro caso: para eso es la comparación de cada ejemplo de arriba. |
dispatch-context |
Una cadena que vuelve en el evento identity-verified para que tu listener sepa qué formulario preguntó. Por omisión, la ruta del wire:model. |
Comportamiento compartido por ambos:
- Bajo el campo, una insignia dice "Verificado" en verde o "No verificado" en ámbar. Responde si este valor se verificó, no si la caja tiene algo escrito.
- Editar un valor verificado cambia la insignia a "No verificado"; volver a escribir el valor verificado la pone verde otra vez, sin código nuevo. Un teléfono se compara como número, así que el número internacional guardado escrito en forma local es el mismo número.
- "Verificar ahora" aparece en cuanto el campo tiene contenido. El correo espera una dirección bien formada y hasta entonces muestra "Formato de correo electrónico no válido"; el teléfono se convierte a formato internacional al pulsar el enlace, y un número que no se puede convertir se rechaza con "Teléfono no válido" en lugar de abrir el diálogo.
- Antes de enviar, el componente revisa que ninguna otra cuenta tenga el valor y muestra el error de unicidad si la hay. Un teléfono también se compara aquí como número, así que encuentra la misma línea ya registrada con su prefijo.
- Tu propiedad enlazada recibe lo que la persona escribe, según lo escribe, y lo conserva entre las idas y vueltas del formulario: nada se vacía y ninguna tecla cuesta una petición. Por eso la verificación es una revisión tuya al enviar el formulario, y no una regla
required; así rechazan el formulario de registro y el perfil un valor al que todavía le falta su código. - Un teléfono se guarda en formato internacional. Un número escrito sin su prefijo se completa con el país de la cuenta con sesión abierta o, cuando no hay nadie dentro, con el país detectado para la visita y luego con
COUNTRY_CODE_FALLBACK.
Reaccionar a una verificación
Cuando un código se confirma, la ventana modal despacha identity-verified con type, email o phone, value, verified_at, context y column, la ruta del wire:model. Los dos canales envían la misma carga. Escúchalo cuando necesites guardar la marca de tiempo:
use Livewire\Attributes\On;
#[On('identity-verified')]
public function onIdentityVerified($data)
{
if (($data['context'] ?? null) !== 'profile') {
return;
}
if ($data['type'] === 'phone') {
$stored = User::find($this->user->getKey());
$stored->phone = normalize_phone_number($data['value']);
$stored->phone_verified_at = $data['verified_at'];
$stored->save();
}
}
Revisa context primero: la ventana modal es compartida y una página puede tener varios de estos campos. Cuando no pasaste dispatch-context, el contexto es la ruta del wire:model, así que el listener compara contra 'user.phone' y no contra un nombre que tú elijas.
Escribe la identidad que se verificó y nada más, sobre un registro leído de la base de datos, como hace el ejemplo. Guardar el modelo completo desde aquí persistiría todos los demás campos tal como estén en ese momento, sin la validación ni las revisiones que corre tu botón de Guardar: una dirección a medio escribir en otro campo quedaría escrita sobre la guardada.
Desarrollo local
El kit no registra los códigos en ningún lado, así que dales un lugar donde caer:
- Correo: pon
MAIL_MAILER=log. El mensaje completo, código incluido, se escribe enstorage/logs/laravel.log, o en el canal deMAIL_LOG_CHANNEL. Recuerda que un reenvío avanza amail_fallbacks, que apunta a Mailgun: sin credenciales de Mailgun el reenvío falla con error, así que vacía el arreglo en local o pide el código una sola vez. Un capturador SMTP local como Mailpit funciona igual conMAIL_MAILER=smtp. - Teléfono: no hay atajo local. Los SMS y las llamadas pasan por el proveedor real, así que usa credenciales reales y tu propio número, o desarrolla con una identidad de correo y deja el teléfono para una pasada posterior.
- La suite de pruebas corre con
MAIL_MAILER=array, definido enphpunit.xml, así que nada sale de la máquina durante las pruebas.