Update for version 5.7
You can use built in feature for activating users. Check how it works on laracast
You can do this on two ways:
- not allow to login at all
- allow with reduced access
I will cover the first case.
In this example I will add mail confirmation on top of the Laravel's default auth scaffolding. It can be applied even if you already have working application.
If starting from scratch create a new laravel project and do the basic auth scaffold.
php artisan make:auth
Create a table which will hold all activation codes.
php artisan make:migration create_user_activations_table --create=user_activations
With these columns
Schema::create('user_activations', function (Blueprint $table) {
$table->integer('user_id')->unsigned();
$table->string('token')->index();
$table->timestamp('created_at');
});
Add one more column to the users table (create new migration or add it to the existing one)
$table->boolean('activated')->default(false);
And start migrations
php artisan migrate
Next, we need ActivationRepository
which will handle activation codes in database.
Method getToken
is taken from Illuminate/Auth/Passwords/DatabaseTokenRepository
class. Other methods will be used in ActivationService.
<?php
namespace App;
use Carbon\Carbon;
use Illuminate\Database\Connection;
class ActivationRepository
{
protected $db;
protected $table = 'user_activations';
public function __construct(Connection $db)
{
$this->db = $db;
}
protected function getToken()
{
return hash_hmac('sha256', str_random(40), config('app.key'));
}
public function createActivation($user)
{
$activation = $this->getActivation($user);
if (!$activation) {
return $this->createToken($user);
}
return $this->regenerateToken($user);
}
private function regenerateToken($user)
{
$token = $this->getToken();
$this->db->table($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;
}
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();
}
}
Now define route to handle request when user clicks on the activation link. Add to routes.php
Route::get('user/activation/{token}', 'Auth\AuthController@activateUser')->name('user.activate');
It is best to put it under the Route::auth()
.
Now create ActivationService
which will wrap logic for creating activation codes and sending emails.
Two main methods are sendActivationMail
and activateUser
which are self explanatory.
Method shouldSend
is checking if email is already sent, or it is sent recently.
Property $resendAfter
is number of hours that needs to pass before we send a now activation email but only if user request it.
You will probably want to change default email sending function to use html template. Replace ->raw
with ->send
.
<?php
namespace App;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\Message;
class ActivationService
{
protected $mailer;
protected $activationRepo;
protected $resendAfter = 24;
public function __construct(Mailer $mailer, ActivationRepository $activationRepo)
{
$this->mailer = $mailer;
$this->activationRepo = $activationRepo;
}
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 <a href="%s">%s</a>', $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();
}
}
AuthController
Override register
method in AuthController
inherited from RegistersUsers
trait. We don't want to immediately log in user but to send them an email instead and keep them logged out. Also we will inject ActivationService
through constructor.
In AuthController
, use classes:
use Illuminate\Http\Request;
use App\ActivationService;
Add protected variable:
protected $activationService;
And override following methods:
public function __construct(ActivationService $activationService)
{
$this->middleware($this->guestMiddleware(), ['except' => 'logout']);
$this->activationService = $activationService;
}
public function register(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
$user = $this->create($request->all());
$this->activationService->sendActivationMail($user);
return redirect('/login')->with('status', 'We sent you an activation code. Check your email.');
}
To show status message add following in login.blade.php
.
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if (session('warning'))
<div class="alert alert-warning">
{{ session('warning') }}
</div>
@endif
If method authenticated
exists in the AuthController it will be called from Laravel trait AuthenticatesUser
. Use this method to block the user if it is not activated yet and, if required, resend the email.
public function authenticated(Request $request, $user)
{
if (!$user->activated) {
$this->activationService->sendActivationMail($user);
auth()->logout();
return back()->with('warning', 'You need to confirm your account. We have sent you an activation code, please check your email.');
}
return redirect()->intended($this->redirectPath());
}
Here, if the user is not activated we will send the activation email again and show the error.
Final method will activate user:
public function activateUser($token)
{
if ($user = $this->activationService->activateUser($token)) {
auth()->login($user);
return redirect($this->redirectPath());
}
abort(404);
}
Reset password functionality is not a problem since the user needs to check email to get the link. If he do so, he confirmed the email.