Building a User Invitation System with Laravel

Published on by

Building a User Invitation System with Laravel image

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.

Joe Dixon photo

Founder and CTO of ubisend. Proud Father to two tiny heroes, Husband, developer, occasional globetrotter.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate logo

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate

Build your SaaS application in hours. Out-of-the-box multi-tenancy and seamless Stripe integration. Supports subscriptions and one-time purchases, allowing you to focus on building and creating without repetitive setup tasks.

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Asymmetric Property Visibility in PHP 8.4 image

Asymmetric Property Visibility in PHP 8.4

Read article
Access Laravel Pulse Data as a JSON API image

Access Laravel Pulse Data as a JSON API

Read article
Laravel Forge adds Statamic Integration image

Laravel Forge adds Statamic Integration

Read article
Transform Data into Type-safe DTOs with this PHP Package image

Transform Data into Type-safe DTOs with this PHP Package

Read article
PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell image

PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell

Read article
Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3 image

Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3

Read article