Capa de post contendo o logo oficial do Laravel Fortify

Verificação por email com Jetstream


novidades jetstream laravel-8 fortify

Uma das novidades do Laravel Jetstream é a verificação por email de novos registros. Com ela podemos indicar se, ao se cadastrar, um usuário deve confirmar seu cadastro utilizando um link de confirmação enviado por email. Vamos entender como fazer isso e como podemos customizar esta funcionalidade.

TL;DR

Se você estiver acompanhando a série sobre o Jetstream e quiser verificar o código utilizado neste post basta acessar o repositório no github e utilizar o branch autenticacao-jetstream, cada alteração feita durante os posts estão em um commit próprio para deixar mais fácil a visualização das alterações =)

Como enviar email de verificação no Laravel 8 usando Jetstream

A funcionalidade de verificação por email vem desabilitada por padrão nas instalações do Laravel Jetstream, e para utilizá-la é necessário realizar alguns passos antes como habilitá-la no array de configurações do Fortify em config/fortify.php, garantir que a model User implemente a interface MustVerifyEmail e ter um servidor SMTP configurado para o disparo de email. Vamos aplicar todos estes requisitos e utilizar esta funcionalidade, chega mais.

Habilitando a funcionalidade de verificação por email

Talvez a parte mais simples deste guia seja habilitar a funcionalidade de verificação por email. Precisamos alterar o arquivo config/fortify.php e adicionar (ou descomentar) a funcionalidade Features::emailVerification(),:

// config/fortify.php

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
    Features::updateProfileInformation(),
    Features::updatePasswords(),
    // Features::twoFactorAuthentication([
    //     'confirmPassword' => true,
    // ]),
],

Implementando a interface necessária

Para que o Laravel saiba (automagicamente) que um email de verificação precisa ser enviado quando um novo usuário se cadastrar no sistema, o Laravel nos dá a opção de indicar que este comportamento será utilizado e ele mesmo (o framework) se encarregará de disparar as ações necessárias. Para isso precisamos assegurar que a model User implemente interface MustVerifyEmail.

A interface MustVerifyEmail já existe no core do Laravel e contém o corpo a seguir:

namespace Illuminate\Contracts\Auth;

interface MustVerifyEmail
{
    /**
     * Determine if the user has verified their email address.
     *
     * @return  bool
     */
    public function hasVerifiedEmail();

    /**
     * Mark the given user's email as verified.
     *
     * @return  bool
     */
    public function markEmailAsVerified();

    /**
     * Send the email verification notification.
     *
     * @return  void
     */
    public function sendEmailVerificationNotification();

    /**
     * Get the email address that should be used for verification.
     *
     * @return  string
     */
    public function getEmailForVerification();
}

Vamos alterar nossa model User para indicar que esta interface está sendo implementada:

// app/Models/User

use Illuminate\Contracts\Auth\MustVerifyEmail;

class User extends Authenticatable implements MustVerifyEmail
…

Pronto! Agora com a funcionalidade de verificação por email habilitada e a model User implementando a interface necessária já estamos com meio caminho andado.

Mas você pode estar se perguntando: vou precisar implementar cada um dos métodos da interface na minha model?

A resposta é: depende.

Caso seja interessante manter controle total sobre as ações de verificação de cadastros por email então sim, será necessário implementar todos os métodos. Caso a ideia seja deixar o Laravel tomar conta disso a resposta é não, não será necessário implementar tudo, na verdade se não quiser não precisa criar a implementação concreta de nenhum dos métodos da interface MustVerifyEmail.

Aí você me pergunta: uai, mas quando utilizamos uma interface nós não somos obrigados a criar TODOS os métodos indicado? Sim! Mas o Laravel já faz isso pra nós, veja só.

Entendendo as “bruxarias”

Parece bruxaria mas não é e aqui vamos entrar em ponto de discussão complicado… Muitos vão dizer que as decisões do Laravel de como fazer algumas “mágicas” é um erro de design. Minha ideia aqui é mostrar como o Laravel faz a “mágica” para não precisarmos implementar os métodos da interface MustVerifyEmail e, a partir disso, cada um poder tirar suas conclusões e decidir se segue com implementação do framework ou se cria sua própria com um design possivelmente mais adequado.

Na nossa model User podemos verificar que está sendo estendida uma outra classe chamada User mas que recebeu um apelido de Authenticatable.

// app/Models/User

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements MustVerifyEmail

Ao navegar para a classe Authenticatable podemos observar a utilização de uma trait chamada (adivinha?) MustVerifyEmail

// Illuminate\Foundation\Auth\User

…
use Illuminate\Auth\MustVerifyEmail;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}

Tcharam! Encontramos como o Laravel faz a “mágica” para não precisarmos implementar a interface MustVerifyEmail.

Agora você pode tomar sua própria decisão de design!

Muitos podem argumentar que é um tanto estranho ter que estender uma classe (classe pai), esta classe que foi estendida utilizar uma trait para que assim a classe filho não precise implementar os métodos concretos de uma interface. O importante é saber que se você não concorda com essa decisão de design basta não estender a classe Authenticatable e implementar você mesmo os métodos da interface MustVerifyEmail para este comportamento.

Neste momento vou preferir a abordagem do Laravel pois entendo que é o que serve para a maioria dos usuários. Mas ainda assim quero ter o mínimo de controle sobre o que está acontecendo, então vamos entender a trait MustVerifyEmail.

Entendendo a trait MustVerifyEmail

A implementação desta trait fica no namespace

Illuminate\Auth\Notifications\VerifyEmail\MustVerifyEmail

Neste momento seria interessante entender como esta trait está fazendo o envio do email de verificação. Podemos visualizar este comportamento no método sendEmailVerificationNotification

Illuminate\Auth\Notifications\VerifyEmail\MustVerifyEmail

    /**
     * Send the email verification notification.
     *
     * @return  void
     */
    public function sendEmailVerificationNotification()
    {
        $this->notify(new VerifyEmail);
    }

Bom, podemos ver que no método de envio da verificação por email está sendo utilizada uma outra classe, a classe VerifyEmail registrada no namespace

Illuminate\Auth\Notifications\VerifyEmail

Recomendo fortemente dar uma olhada na implementação desta classe para ninguém se sentir perdido caso seja necessário fazer alguma alteração ou adicionar alguma regra. Em resumo o que é feito na classe é:

1) É criado um link para verificação com uma validade de 60 minutos ou conforme indicado na configuração em auth.verification.expire:

    protected function verificationUrl($notifiable)
    {
        return URL::temporarySignedRoute(
            'verification.verify',
            Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
            [
                'id' => $notifiable->getKey(),
                'hash' => sha1($notifiable->getEmailForVerification()),
            ]
        );
    }

2) É utilizado um template simples de email com os dados para seu envio:

    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
        }

        return (new MailMessage)
            ->subject(Lang::get('Verify Email Address'))
            ->line(Lang::get('Please click the button below to verify your email address.'))
            ->action(Lang::get('Verify Email Address'), $verificationUrl)
            ->line(Lang::get('If you did not create an account, no further action is required.'));
    }

Caso estas regras não atendam as necessidades, implemente você mesmo o método sendEmailVerificationNotification na model User e seja feliz =)

Agora que já entendemos os comportamentos das classes e já decidimos como queremos utilizar a verificação por email (regra própria ou através das “mágicas” do Laravel) está tudo pronto! Vamos acessar a página de cadastro em /register e nos registrar para recebermos o link de confirmação no email.

Resolvendo erro de SMTP

Ao enviar o formulário de cadastro, podemos nos deparamos com um erro do SwiftTransport

Swift_TransportException
Cannot send message without a sender address
http://jetstream.test/register

Página exibindo o erro da classe SwiftTransport com a mensagem ‘Cannot send message without a sender address’

Este erro ocorre pelo fato de não existir as configurações de SMTP ainda e especificamente neste caso porque não foi configurado o endereço de email do remetente. Vamos resolver este problema então.

No arquivo .env há uma sessão sobre as configurações de email:

// .env
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"

Aqui devemos colocar os dados do servidor SMTP (ou outro tipo caso seja utilizado) e o remetente padrão. Para facilitar vamos utilizar o servidor o Google (que é gratuito) em uma conta gmail. No meu caso vou indicar como remetente padrão a conta [email protected].

Para não me estender neste post criei um guia passo-a-passo sobre como utilizar o servidor SMTP do Google com Laravel.

Após seguir os passos de como ativar este serviço com uma conta gmail, vamos precisar alterar o arquivo .env para utilizar o SMTP do gmail:

// .env

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=minha_senha_segura
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"

Neste exemplo utilizei a conta do [email protected] e obviamente a senha acima não é a senha do email (=P), não esqueça de atualizar seu .env colocando as credenciais da conta gmail em que você ativou o serviço.

Agora ao preencher a tela de cadastro novamente somos direcionados para uma página diferente quando o cadastro é feito com sucesso

Exemplo da página que o usuário é redirecionado ao se cadastrar no sistema contendo a informação que um email de verificação foi enviado, botões de ações de logout e enviar email novamente

E ao verificar a caixa de email da conta utilizada no cadastro deverá existir um email contendo o link para ativação da conta

Print do email enviado para um novo usuário com o link de verificação

Basta clicar no botão de verificação do email ou utilizar o link ao final da página e já era! Temos a verificação por email de cadastro de usuários completa.

Ao clicar no link o usuário será redirecionado para a rota indicada como padrão na aplicação e terá seu registro validado. No banco de dados, este novo registro na tabela users terá o campo email_verified_at preenchido com o datetime da verificação.

Consideração importante

Na primeira tentativa de cadastro o servidor SMTP ainda não estava configurado e tivemos um erro (conforme pode ser visto no primeiro print logo no começo deste post), porém, o usuário foi cadastrado no banco de dados e caso o mesmo endereço de email tente se cadastrar novamente será gerado um erro de validação dizendo que o email já foi cadastrado.

Este comportamento acontece por conta da ordem em que o Jetstream faz as ações, caso queira queira realizar uma tratativa neste sentido, recomendo a utilização de transactions acima das ações do cadastro, assim qualquer erro que ocorrer a ação pode ser desfeita caso seja necessário. Outra possível solução é colocar o envio de email numa fila e tratar as ações da fila que tiveram falha.

Caso alguma destas soluções lhe agrade deixe-me saber , em breve posso trazer mais conteúdos relacionados!

Conclusão

A funcionalidade de verificação por email no Laravel Jetstream é uma mão na roda pra quem precisa de algo rápido e prático porém, caso seja necessário realizar muitas alterações, pode ser um pouco trabalhoso implementar todos os métodos para usar direto no Jetstream, cabe a cada um entender o momento e implementar a solução que mais lhe agrade!

-- Up the Laravel's \o/

← Post Anterior
Próximo Post →