DEV Community

Florian Wartner
Florian Wartner

Posted on

Laravel 5.6 - User Activation with Signed Routes & Notifications

We’ve seen a lot of packages, which are helping us to easily implement a user-activate feature into our apps.

Well.. There’s a way simpler way with only Laravel internals.

What we’re going to cover in this article:

  • Laravel Notifications
  • Laravel Signed URLs
  • Basic Middleware Usage

Let’s start!

Note: I assume that you have the Laravel/installer globally installed on your machine.

First of all, we need a clean application:
laravel new my-project-name

Now lets open the new project in your IDE of choice..
Once you’ve setup up the project, we can directly jump into the actual „magic“..!

But first of all, we need to add the basic-auth stuff to our application:

php artisan make:auth

Wait..! What are we going to achieve?

Well.. This is simple. We’re trying to provide a simple way to let our users verify their account by using the Laravel Notifications and Signed URLs

No more repeats on what we’re doing here!

Set up your user migration to provide a Boolean, that we can check later if the user is verified or not by adding this line:

$table->boolean('is_activated')->default(false);

After that, our user migration should look like this:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->boolean('is_activated')->default(false);
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

Now we need to set up our User model like this:

...
protected $fillable = ['name', 'email', 'password', 'is_activated'];
...
protected $casts = ['is_activated' => 'boolean'];
...

So the user model should look like this:

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'is_activated'
    ];

    protected $casts = [
        'is_activated' => 'boolean'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

Now we can update the is_activated field directly.

Next up, we need a new notification:
php artisan make:notification Users/ActivateEmailNotification

Lets move on to our notification..

What we want to do next is using the created notification class to send the user a link that he need to click when he would like to use our application (which is secured by the new feature I will show you in a few moments..).

So lets create our url:

<?php

namespace App\Notifications\Users;

use App\User;
use Illuminate\Support\Facades\URL;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;

class ActivateEmailNotification extends Notification
{
    use Queueable;

    /**
     * User Object
     *
     * @var App\User
     */
    public $user;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        $url = URL::signedRoute('activate-email', ['user' => $this->user->id]);

        return (new MailMessage)
                    ->subject('Activate your email address')
                    ->line('In order to use the application, please verify your email address.')
                    ->action('Activate your account', $url)
                    ->line('Thank you for using our application!');
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

We´re using the Laravel Signed URL Feature to create the url for the user. This helps us to make sure that only the user where the email was sent to, has the ability to verify his email-address.

We can also say, that the url is only valid for a specified amount of time.

For this we need to change the url from

$url = URL::signedRoute('activate-email', ['user' => $this->user->id]);

To

$url = URL::temporarySignedRoute('activate-email', now()->addMinutes(30), ['user' => $this->user->id]);

Now the url will be valid for just 30 minutes after creation.

Next up we need to modify our Auth\RegisterController.
Simply add 'is_active' => false, to the create method at the bottom of the controller. In the same method, we´re going to notify the user.

The controller will look like this:

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'is_activated' => false,
            'password' => Hash::make($data['password']),
        ]);

        $user->notify(new \App\Notifications\Users\ActivateEmailNotification($user));

        return $user;    }
}

We also need a middleware to determine, if the user is activated or not.

Create one with the command:
php artisan make:middleware CheckUserActivated

Modify the middleware like this:

<?php

namespace App\Http\Middleware;

use Closure;

class CheckUserActivated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (!$request->user()->is_activated) {
            return redirect('/not-enabled'); // You can add a route of your choice
        }

        return $next($request);
    }
}

Now we need to register our new middleware to the kernel.
Open App\Http\Kernel.php and add this to the routeMiddleware at the bottom of the class:

'active' => \App\Http\Middleware\CheckUserActivated::class,

We need to add another class to this file:

'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,

Now we´re able to determine if the user is active or not. I case he's not active, we want to redirect him to another page, where you can add a note for the user, that he still need to activate his account using the link in our email.

In our notification, we added a url for the user to confirm his email-address.
In order to use the link, we need to add this route in our web-routes file:

Route::get('/activate-email/{user}', function (Request $request) {
    if (!$request->hasValidSignature()) {
        abort(401, 'This link is not valid.');
    }

    $request->user()->update([
        'is_activated' => true
    ]);

    return 'Your account is now activated!';
})->name('activate-email');

You can put this method into a separate controller if you like. But I like to keep it simple here.

This route will activate the user and let him know, that he's no able to use the application.

Conclusion

I really hope you liked this short tutorial about user activation with simple helpers directly from Laravel.

If so, please feel free to share this article and spread the love ❤️

And of course make sure that you follow me on Twitter

The source of this article is available on GitHub 😺

Top comments (4)

Collapse
 
alexcaglairi profile image
alexcaglairi • Edited

Hi,
i followed all of your step but the middleware doesn't workkkif i attempt to login using a user with is_activated with false value the login continue successfully.
it's as if the middleware was not registered.
I have also compared your package with my source and are identical.
What could be wrong?

Collapse
 
marcesdan profile image
marcesdan

Excelent!

But I ask, should the route be a get instead of post?

Thanks for sharing!

Collapse
 
nepktoab profile image
Aneel

Hi,
$request->user() gives Call to a member function update() on null

Collapse
 
mohammedmohammedbayomy profile image
Mohammed Bayomy

I Think That Now You're Using Another Table Not The users Table , Cuz user() function Will Work Only To Check Any Values In The users Table
So You Should Send What You'll Need In The Method Of toMail(){ .... }

Like

$url = URL::temporarySignedRoute('activate-email', now()->addMinutes(60), [
'register' => $this->register->id,
'status'=> $this->register->status,
'role_id' =>$this->register->role_id,
'email' =>$this->register->email,
]);
return (new MailMessage)
->line('Please Verify Your Email Address')
->action('Activate Email', $url)
->line('Thank you for using our application!');

And To Check The Value In The active Route Or In The Middleware You Type It Easily

Like Now I Want To Change The Value Of Status When User Click In The Link Inside The Sent Email

$request->register->update([
'status' => 1
]);

Hope It Help Somebody

_^