Per un mio progetto in corso avevo la necessità di verificare la mail qualvolta un utente si registrasse tramite il modulo standard di Laravel.
Laravel prevede una sua funzionalità di registrazione consultabile al questo link:
https://laravel.com/docs/5.8/verification
Però ho rilevato che la funzionalità implementata da Laravel ha dei grossi limiti, in particolare è necessario che l’utente sia loggato per accedere al pannello per inviare la mail di conferma.
Inoltre il sistema agisce a livello di midleware, bloccando le route sotto una certo middleware che richiede la verifica del profile e non agisce in modo globale.
Quello che occorreva a me è che una volta registrato venisse inviata la mail di conferma senza che l’utente dovesse necessariamente essere loggato nell’applicativo, oltretutto avevo necessità che qualvolta l’utente non verificato su loggasse, l’applicativo oltre a bloccare l’accesso, consentisse invio di nuovo della mail di conferma.
Per questo è stato necessario creare una nuova funzionalità che ora vediamo nel dettaglio.
Per prima cosa dobbiamo generare le tabelle e le modifiche a quelle esistenti per poter gestire la verifica dell’utente.
Creazione tabella con i codici di verifica
Dobbiamo creare una nuova tabella nel nostro database che contiene il codice di verifica, e un altro codice univoco che servirà per generare una nuova mail di conferma. Questo codice viene inviato all’indirizzo e-mail dell’utente per l’attivazione dell’account. Ora creiamo il modello assieme alla rispettiva migration:
php artisan make:modal VerifyUser -m
Modifica tabella con i codici di verifica
Ora è il momento di aggiornare il file di migrazione verify_users.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateVerifyUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('verify_users', function (Blueprint $table) {
$table->unsignedInteger('user_id');
$table->string('token');
$table->string('uid');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('verify_users');
}
}
Creazione migration e modifica tabella users
Ora è necessario aggiungere un attributo alla tabella Users che indicherà se l’utente è verificato oppure no.
Eseguiamo questo comando per generare la migrations.
php artisan make:migration add_verified_to_users_table --table=users
e andiamo a modificarla in questo modo:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddVerifiedToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('verified')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('verified');
});
}
}
Relazione tra tabella users e verify_users
Ora aggiungiamo una migrazione per creare la relazione tra la tabella utente e la tabella users dove sono contenuti i codici di attivazione verify_users.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddRelationshipVerifyUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('verify_users', function (Blueprint $table) {
$table->foreign('user_id','FK_verify_users_user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('verify_users', function (Blueprint $table) {
$table->dropForeign('FK_verify_users_user_id');
});
}
}
Migrazione
A questo punto eseguiamo la migrazione con:
php artisan migrate
Ora possiamo procedere con la creazione dei modelli, controller e quant’altro.
Relazioni nel modello
Ora che le nostre tabelle sono pronte, possiamo andiamo avanti e specifichiamo la relazione uno a uno tra la tabella utente users e verify_user.
Aggiungiamo il seguente metodo al modello User.php.
public function verifyUser()
{
return $this->hasOne('App\VerifyUser');
}
Aggiorniamo la seguente classe del modello VerifyUser.php creata in precedenza.
class VerifyUser extends Model
{
protected $guarded = [];
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
}
Invio mail di verifica
Per inviare email assicurati di avere le proprietà della tua posta configurate nel file .env del tuo progetto.
Ora generiamo una classe per la verifica dell’email dell’utente al momento della registrazione. Generiamo una classe con comando artisan sottostante.
php artisan make:mail VerifyMail
Dopo l’esecuzione del comando, verrà generata una nuova classe denominata VerifyMail.php in App/Mail. Questa classe avrà un metodo di compilazione che definisce quale blade utilizzare per inviare una e-mail.
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class VerifyMail extends Mailable
{
use Queueable, SerializesModels;
public $user;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('emails.verifyUser');
}
}
Creeremo una cartella e-mail sotto resources/view in cui manterremo le nostre viste e-mail. Inoltre, come puoi vedere, abbiamo dichiarato $user come attributo pubblico e stiamo impostando la variabile nel costruttore. Le variabili sono dichiarate pubbliche come disponibili per impostazione predefinita nel file di visualizzazione. Pertanto è possibile fare riferimento direttamente alla variabile per ottenere i dati relativi all’utente.
Andiamo avanti e creiamo il testo che verrà incluso nella mail con il link di conferma. Crea un nuovo file verifyUser.blade.php nel percorso resources/view/email e aggiungi i seguente contenuto.
<!DOCTYPE html>
<html>
<head>
<title>Mail conferma</title>
</head>
<body>
<h2>Benvenuto nel sito {{$user['name']}}</h2>
<br/>
La tua mail di registrazione è {{$user['email']}}, Per accedere con il tuo account è necessario confermare la mail attraverso il link sottostante.
<br/>
<a href="{{url('user/verify', $user->verifyUser->token)}}">Verifica Email</a>
</body>
</html>
Ora dal momento che abbiamo il codice necessario pronto per inviare l’e-mail di verifica all’utente. Modifichiamo la nostra classe RegistrationController per inviare email. Questa classe si trova in App/Http /Controller/Auth e modifica il metodo di creazione come indicato di seguito:
protected function create(array $data)
{
//return User::create([
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
//add to custom mail verification
$verifyUser = VerifyUser::create([
'user_id' => $user->id,
'token' => sha1(time()),
'uid' => md5(time())
]);
Mail::to($user->email)->send(new VerifyMail($user));
return $user;
}
Stiamo generando un nuovo token casuale usando sha1 (time ()) e memorizzandolo nella tabella confirm_users e user_id, dopodiché utilizziamo la facciata Mail per inviare VerifyMail agli utenti. Inolte in questa fase genereremo un codice univoco uid per poter reinviare la mail di conferma in sicurezza nelle fasi successive.
Configurazione route e controller per verifica email
Ora funzionalità di verifica dell’utente che è il codice che verrà eseguito quando l’utente fa clic sul collegamento inviato al suo account di posta elettronica.
Crea un percorso per il token di verifica.
Route::get('/user/verify/{token}', 'Auth\RegisterController@verifyUser')->name('user.verify');
Poiché la funzionalità è correlata alla registrazione utente, creeremo un nuovo metodo verificaUser in RegisterController.php.
public function verifyUser($token)
{
$verifyUser = VerifyUser::query()->where('token', $token)->first();
if (isset($verifyUser)) {
$user = $verifyUser->user;
if (!$user->verified) {
$verifyUser->user->verified = 1;
$verifyUser->user->save();
VerifyUser::query()->where('token', $token)->delete();
$status = "La tua e-mail è verificata. Ora puoi accedere.";
} else {
$status = "La tua e-mail è già stata verificata. Ora puoi accedere.";
}
} else {
return redirect('/login')->with('warning', "Spiacenti, la tua email non può essere identificata.");
}
return redirect('/login')->with('status', $status);
}
Il metodo verificatore accetta un token dall’URL e va avanti e trova l’utente associato a quel token. Conferma che l’utente esiste e non è stato ancora verificato, quindi procede e modifica lo stato di verifica nel database. Vari messaggi di stato e di avviso vengono visualizzati quando si reindirizza l’utente da visualizzare nel file di visualizzazione.
Bloccare gli utenti non verificati in fase di registrazione e login.
Ora abbiamo il nostro processo di verifica e-mail e attivazione dell’account utente. Ora abbiamo bisogno di un’altra cosa importante da fare prima di contrassegnare questo come completo.
Non dovremmo consentire all’utente non verificato di accedere alle pagine dell’applicazione fino a quando non saranno verificate. Pertanto, dobbiamo applicare i controlli in due punti “dopo l’accesso dell’utente” e “dopo la registrazione di un nuovo utente”.
Quindi modifichiamo il metodo authenticated nel controller LoginController.php.
protected function authenticated(Request $request, $user)
{
if (!$user->verified) {
auth()->logout();
$verifyUser = VerifyUser::query()->where('user_id','=',$user->id)->first();
if(isset($verifyUser))
return back()->with('warning', "Devi confermare il tuo account. Ti abbiamo inviato un codice di attivazione,
controlla la tua email. Nel caso tu non abbia ancora ricevuto la mail di attivazione clicca
<a href=\"user/resend/".$verifyUser->uid."\">qui</a>");
else
return back()->with('warning', "Non riusciamo a confermare il tuo account. Contatta l'assistenza.");
}
$continue = $request->get('continue');
if(isset($continue))
return redirect($continue);
return redirect($this->redirectTo);
}
Il metodo autenticato viene eseguito subito dopo l’autenticazione dell’utente. Sostituiremo questo e lo useremo per verificare se l’utente è attivato. Altrimenti lo disconnetteremo e lo rimanderemo alla pagina di accesso con il messaggio di avviso.
Come potete vedere il controller restituite anche il link per poter reinviare di nuovo la mail all’utente qualora non fosse arrivata. Nel punto successivo vedremmo come configurare questo invio.
Modifichiamo il controller RegisterController.php e sovrascriviamo il metodo registrato da RegistersUsers
protected function registered()
{
$this->guard()->logout();
return redirect('/login')->with('status', 'Ti abbiamo inviato un codice di attivazione. Controlla la tua email e fai clic sul link per verificare.');
}
Il metodo registered viene eseguito subito dopo la registrazione dell’utente nell’applicazione, lo sostituiremo e lo modificheremo per disconnetterlo e inviarlo nuovamente al login con il messaggio di stato.
Infine, dobbiamo modificare il nostro file login.blade.php che si trova in resources/view/auth aggiungendoci il seguente codice per visualizzare i messaggi di stato restituiti.
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if (session('warning'))
<div class="alert alert-warning">
{{ session('warning') }}
</div>
@endif
Reinvio mail di verifica
Un importante funzione è quella di poter reinviare la mail di conferma anche successivamente, in questo caso verrà proposto l’invio ad ogni login.
Per questo aggiungiamo una route dedicata
Route::get('/user/resend/{mail}', 'Auth\RegisterController@resendVerification')->name('user.resendverifymail');
Ora aggiungiamo il nostro metodo resendVerification al controller RegisterController che si occuperà di generare una nuova mail di attivazione.
public function resendVerification($email)
{
try {
$verifyUser = VerifyUser::query()
->where('uid', '=', $email)
->get()->first();
if (isset($verifyUser)) {
$user = User::query()->where('id', '=', $verifyUser->user_id)->get()->first();
Mail::to($user->email)->send(new VerifyMail($user));
return redirect('/login')->with('status', 'Ti abbiamo inviato un codice di attivazione. Controlla la tua email e fai clic sul link per verificare.');
} else {
return redirect('/login')->with('status', 'Errore nella richiesta. Contatta l\'assistenza.');
}
} catch (\Exception $e) {
return redirect('/login')->with('status', 'Errore nella richiesta. Contatta l\'assistenza.');
}
}
Qualora l’utente facesse il login, verra restituito il messaggio di errore con il link per reinviare la mail di conferma. Per ragioni di sicurezza, verrà utilizzato il codice uid per poter verificare e inviare la mail di conferma all’utente.
Conclusion
In questo articolo, abbiamo discusso della verifica e-mail personalizzata per gli utenti come alternativa alla funzionalità integrata di Laravel.
Ciao. Complimenti per lo snippet veramente molto completo. Io sono alle prime armi con Laravel e, seguire questi tutorial mi aiuta a comprendere meglio.
Volevo comunque segnalarti un errore che mi da in fase di testing:
Class ‘App\Http\Controllers\Auth\Hash’ not found
Infatti, la classe Hash non la trovo. Ho sbagliato qualcosa?
Grazie in anticipo e ancora tanti complimenti.
Ciao Franco,
dall’errore che mi riporti credo che il problema sia dovuto dalla mancanza del namespace necessario per richiamare il metodo make() . E’ necessario che verifichi che nel RegistrationController sia presente questo prima della classe del controller:
use Illuminate\Support\Facades\Hash;
In caso mi serve sapere anche la versione di Laravel che stai usando.
Se sei nuovo ti consiglio in caso di usare phpstorm come IDE in cui è presente anche l’integrazione per Laravel, oltre a segnalarti gli errore in modo migliore rispetto ad altri die, aggiunge lui automaticamente i namespace in caso fossero mancanti.
Grazie e buona serata.