Building a User Invitation System with Laravel

In the not too distant past at ubisend, we needed to build a solution to provide our users the ability to invite additional users to manage their account.

This is a pretty common problem.

Think about a CMS. It’s not much use if you are the only person able to create new content and you certainly don’t want to share your password to allow someone to proof an article.

Similarly, imagine you are building a multitenanted application and want to give your users the ability to add additional members to their team.

Sure, you could build out some CRUD methods for managing users, but wouldn’t it better if you could allow those users to set their own passwords rather than having to send it to them by email, insecurely, in plaintext?

This article will walk you through one approach to building this functionality.

Housekeeping

This task could be carried out in a multitude of ways. At its simplest, we could create a new user record (flagging it inactive) and store a token which is then used in a link to activate the account.

Here, though, we will tackle the problem in a different way using an additional invites table where we will store the user’s email address and activation token. Upon activation, we will use this data to create the new user.

We’ll be using a fresh install of Laravel 5.4, and you will need to have configured your database and mail settings.

Migrations

A default Laravel installation will give us a leg-up for the user migration as it ships by default with the framework.

For this tutorial, remove the name and password fields from the migration:

public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('email')->unique(); $table->rememberToken(); $table->timestamps(); }); }

We do need to create a migration for our invites. On the console from the root of your project, run php artisan make:migration create_invites_table .

This command will create a new migration file in the database/migrations directory. We’ll need to define an incrementing ID, a field to store the new user’s email address and a field for the unique token the new user will use when accepting the invite.

public function up() { Schema::create('invites', function (Blueprint $table) { $table->increments('id'); $table->string('email'); $table->string('token', 16)->unique(); $table->timestamps(); }); } public function down() { Schema::drop('invites'); }

Now, on the console from the root of your project, run php artisan migrate .

The database is good to go.

Models

We will need to create Eloquent models for managing both our team and user records.

As with migrations, Laravel ships with a default Eloquent user model so no need to create one.

For the invites model, head back to the console, and from the root of your project run php artisan make:model Invite .

Take a look in the app directory, and you will see your newly created invite model.

All we need to do here is define the fillable fields as follows. This will allow us to mass assign properties when creating updating models.

protected $fillable = [ 'email', 'token', ];

Routes

For this tutorial we’ll need to define three routes for the following scenarios:

Show the form to invite a new user

Process the form submission

Accept the invitation

In the app/routes/web.php file, add the following routes:

Route::get('invite', 'InviteController@invite')->name('invite'); Route::post('invite', 'InviteController@process')->name('process'); // {token} is a required parameter that will be exposed to us in the controller method Route::get('accept/{token}', 'InviteController@accept')->name('accept');

Controller

The eagle-eyed readers will have noticed when defining the routes above we referenced InviteController to process the requests.

That controller can, again, be created using artisan . On the console from the root of your project, run php artisan make:controller InviteController .

Open up app/Http/Controllers/InviteController.php and define the following methods:

public function invite() { // show the user a form with an email field to invite a new user } public function process() { // process the form submission and send the invite by email } public function accept($token) { // here we'll look up the user by the token sent provided in the URL }

Great, everything is set up. We can now flesh out these methods and make our invitation system come to life.

Business Logic

Here, we will work through each of the methods stubbed out above.

invite()

This is nice and simple. We need to return a view which presents a form to the user where they can enter the email address of the invitee.

The method will look something like this:

public function invite() { return view('invite'); }

Create the file resources/views/invite.blade.php . This view will need to contain a form that posts to /invite with an input called email:

// make use of the named route so that if the URL ever changes, // the form will not break #winning <form action="{{ route('invite') }}" method="post"> {{ csrf_field() }} <input type="email" name="email" /> <button type="submit">Send invite</button> </form>

process()

This is where the bulk of our work will be carried out.

As part of this method, we need to notify the new user that someone has sent them an invitation. To do that, let’s make use of Laravel’s mailables.

To start, we need to create a mailable class. On the console from the root of your project, run php artisan make:mail InviteCreated .

This will generate a new class called, you guessed it, InviteCreated in your app/Mail directory. Open up this class and update the constructor to accept an invite model and assign this as a public property.

use App\Invite; public function __construct(Invite $invite) { $this->invite = $invite; }

Now, to send the email, define a build method. This will determine who to send the email from and which view to use to generate the email.

public function build() { return $this->from('you@example.com') ->view('emails.invite'); }

Next, we need to generate said view file. Create the file resources/views/emails/invite.blade.php . We’ll keep this super simple:

<p>Hi,</p> <p>Someone has invited you to access their account.</p> <a href="{{ route('accept', $invite->token) }}">Click here</a> to activate!

“How do we have access to the $invite variable in our view,” I hear you cry! Laravel will automatically make all public properties of your mailable class available to your views.

Now, back to our InviteController , we will need to generate a unique random token which can be used to identify the new user when they accept the invitation. We will store this record in the invites table along with the email address provided by the person carrying out the invite.

use App\Invite; use App\Mail\InviteCreated; use Illuminate\Support\Facades\Mail; ... public function process(Request $request) { // validate the incoming request data do { //generate a random string using Laravel's str_random helper $token = str_random(); } //check if the token already exists and if it does, try again while (Invite::where('token', $token)->first()); //create a new invite record $invite = Invite::create([ 'email' => $request->get('email'), 'token' => $token ]); // send the email Mail::to($request->get('email'))->send(new InviteCreated($invite)); // redirect back where we came from return redirect() ->back(); }

Nice work! That’s our new invite stored, and the new user notified.

accept()

Finally, we need to allow our new users to accept the invite. This is what will happen when the user clicks the activation email we sent above.

Usually, you would probably want to capture a password and any other user details you may need at this point, but to keep this simple, we’re just going to check for the existence of the token and create the user accordingly.

Remember, the token in the URL will be passed to us as a parameter from Laravel.

use App\User; use App\Invite; use App\Mail\InviteCreated; use Illuminate\Support\Facades\Mail; ... public function accept($token) { // Look up the invite if (!$invite = Invite::where('token', $token)->first()) { //if the invite doesn't exist do something more graceful than this abort(404); } // create the user with the details from the invite User::create(['email' => $invite->email]); // delete the invite so it can't be used again $invite->delete(); // here you would probably log the user in and show them the dashboard, but we'll just prove it worked return 'Good job! Invite accepted!'; }

Give It a Go!

Hit the invite URL in your browser (e.g. http://example.com/invite), enter the email address of the person you are inviting and submit the form.

Take a look in the invites table of your database; you should see new record has been created with a new unique token. Now check your email; you should have received a message containing a link to activate the new user which contains the token stored in the database.

Finally, click the link and you will be greeted with

“Good job! Invite accepted!” which is a good sign. To check everything worked as expected, the invite record should no longer be in the database, but instead, a new user record should have appeared in the users table.

That’s a Wrap

Nice work! You have successfully implemented a user invitation system.

Although this is a simple example, it provides a good base to build from.

It would be trivial to extend this example to capture new user details at the point they accept the invitation as well as allowing invites to be revoked and/or re-sent.

Additionally, you could modify the code to support multitenancy or team/user relationships—though this would be somewhat more complex.