Laravel User Registration with Email Activation
This post documents how to add an e-mail confirmation to the Laravel User registration that is generated by the Artisan console. In this example, the registered user will not be able to login until the activation link that is sent to the users registered e-mail account has been loaded into the browser.
Requirements
An instance of Laravel 5.2 setup for local development with authentication scaffolding is required. Refer to the first page of my recent post on Laravel User Authentication with Ajax Validation for more information on installing Laravel 5.2 using Composer and generating the authentication scaffolding with artisan.
User Model
I like placing model classes in their own directory. This can be done with just a few minor changes after moving the User class into a new app/Models folder.
mkdir app/Models
mv app/User.php app/Models
Edit app/Models/User.php, update the App namespace to App\Models.
User.php
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
...
Throughout this tutorial you will encounter an ellipsis … in the code examples. These are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, checkout the email-activation branch in the source code.
Edit app/Http/Controllers/Auth/AuthController.php, update use App\User to use App\Models\User.
AuthController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller
{
...
Edit, config/auth.php, update the authentication drivers user provider.
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
REPLACE
'model' => App\User::class,
WITH
'model' => App\Models\User::class,
Database Migration Files
Generate a database migration file for creating the user_activations table. This table stores the activation codes until they are used.
create_user_activations_table
php artisan make:migration create_user_activations_table --create=user_activations
Edit the migration file up function to create these three fields in the user_activations table.
public function up()
{
Schema::create('user_activations', function (Blueprint $table) {
$table->integer('user_id')->unsigned();
$table->string('token')->index();
$table->timestamp('created_at');
});
}
alter_users_table_add_activated_col
Generate a database migration file for adding a column to the users table.
php artisan make:migration alter_users_table_add_activated_col
Edit the migration file up function to add the activated field to the users table.
public function up()
{
Schema::table('users', function($table) {
$table->boolean('activated')->default(false);
});
}
Run the migration
php artisan migrate
ActivationRepository Class
Create a Repositories folder in the app directory
mkdir app/Repositories
Create the ActivationRepository class file in app/Repositories. This class contains functions for generating activation tokens and updating the tables in the database.
ActivationRepository.php
<?php
namespace App\Repositories;
use Carbon\Carbon;
use Illuminate\Database\Connection;
class ActivationRepository
{
protected $db;
protected $table = 'user_activations';
public function __construct(Connection $db)
{
$this->db = $db;
}
public function getActivation($user)
{
return $this->db->table($this->table)->where('user_id', $user->id)->first();
}
public function getActivationByToken($token)
{
return $this->db->table($this->table)->where('token', $token)->first();
}
public function deleteActivation($token)
{
$this->db->table($this->table)->where('token', $token)->delete();
}
public function createActivation($user)
{
$activation = $this->getActivation($user);
if (!$activation) {
return $this->createToken($user);
}
return $this->regenerateToken($user);
}
protected function getToken()
{
return hash_hmac('sha256', str_random(40), config('app.key'));
}
private function regenerateToken($user)
{
$token = $this->getToken();
$this->db->table($this->table)->where('user_id', $user->id)->update([
'token' => $token,
'created_at' => new Carbon()
]);
return $token;
}
private function createToken($user)
{
$token = $this->getToken();
$this->db->table($this->table)->insert([
'user_id' => $user->id,
'token' => $token,
'created_at' => new Carbon()
]);
return $token;
}
}
ActivationFactory Class
Create a Factories folder in the app directory
mkdir app/Factories
Create the ActivationFactory class file in app/Factories.
ActivationFactory.php
<?php
namespace App\Factories;
use App\Models\User;
use App\Repositories\ActivationRepository;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\Message;
class ActivationFactory
{
protected $activationRepo;
protected $mailer;
protected $resendAfter = 24;
public function __construct(ActivationRepository $activationRepo, Mailer $mailer)
{
$this->activationRepo = $activationRepo;
$this->mailer = $mailer;
}
public function sendActivationMail($user)
{
if ($user->activated || !$this->shouldSend($user)) {
return;
}
$token = $this->activationRepo->createActivation($user);
$link = route('user.activate', $token);
$message = sprintf('Activate account %s', $link, $link);
$this->mailer->raw($message, function (Message $m) use ($user) {
$m->to($user->email)->subject('Activation mail');
});
}
public function activateUser($token)
{
$activation = $this->activationRepo->getActivationByToken($token);
if ($activation === null) {
return null;
}
$user = User::find($activation->user_id);
$user->activated = true;
$user->save();
$this->activationRepo->deleteActivation($token);
return $user;
}
private function shouldSend($user)
{
$activation = $this->activationRepo->getActivation($user);
return $activation === null || strtotime($activation->created_at) + 60 * 60 * $this->resendAfter < time();
}
}
Route
Create a route for the e-mail activation link in app/Http/routes.php. This user.activate route should be added after Route::auth()
routes.php
<?php
...
Route::auth();
Route::get('user/activation/{token}', 'Auth\AuthController@activateUser')->name('user.activate');
AuthController
Make the following updates to app/Http/Controllers/Auth/AuthController.php
REPLACE
use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
WITH
use App\Factories\ActivationFactory;
use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Http\Request;
REPLACE
public function __construct()
{
$this->middleware($this->guestMiddleware(), ['except' => 'logout']);
}
WITH
protected $activationFactory;
public function __construct(ActivationFactory $activationFactory)
{
$this->middleware($this->guestMiddleware(), ['except' => 'logout']);
$this->activationFactory = $activationFactory;
}
At the bottom of the controller, add these three functions after the create function. The register function we are adding will override the register method inherited from the AuthenticatesAndRegistersUsers trait.
...
public function register(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
$user = $this->create($request->all());
$this->activationFactory->sendActivationMail($user);
return redirect('/login')->with('activationStatus', true);
}
public function activateUser($token)
{
if ($user = $this->activationFactory->activateUser($token)) {
auth()->login($user);
return redirect($this->redirectPath());
}
abort(404);
}
public function authenticated(Request $request, $user)
{
if (!$user->activated) {
$this->activationFactory->sendActivationMail($user);
auth()->logout();
return back()->with('activationWarning', true);
}
return redirect()->intended($this->redirectPath());
}
Authentication Language Lines
For the activation status messages that we need to display to the user, update the resources/lang/en/auth.php file.
en/auth.php
<?php
return [
...
'activationStatus' => 'We sent you an activation code. Please check your e-mail.',
'activationWarning' => 'You need to confirm your account. We have sent you an activation code, please check your e-mail.',
...
];
Add the following inside the form at resources/views/auth/login.blade.php Blade view to render the activation messages.
login.blade.php
...
@if (session('activationStatus'))
<div class="alert alert-success">
{{ trans('auth.activationStatus') }}
</div>
@endif
@if (session('activationWarning'))
<div class="alert alert-warning">
{{ trans('auth.activationWarning') }}
</div>
@endif