Laravel Passport and Hyn\Tenancy — Part 1



This is the post you have all been waiting for. I have been working on building my SaaS application for three months and most of this time has been spent going into what seems like uncharted territory. There are three main packages I was trying to get working together: Laravel, Tenancy, and Passport.
Edit — This will work with Laravel 5.7 and Hyn\Multi-tenant 5.3
This article is part of a Series. Check out the others below!

Part 0 — Laravel Multi-Tenant App Setup
Part 1 — Laravel Passport and Hyn\Tenancy←You are Here
Part 2 — VueJS and Laravel API
Part 3 — Laravel Multi-Tenant Testing
Seems easy right? Well… there aren’t many resources that show how to best utilize these packages together.
So for this demo I will be creating a small ticketing system. Once a user signs in they will see a list of tickets and will be able to CRUD operations on the tickets. The Front End will be Vuejs and it will talk to our Laravel API.
Buckle up and hang onto your hats because we are going to cover it all right here.




Also special thanks to Ashok Gelal for writing this series. It helped me tremendously early on trying to figure out the hyn\multi-tenant package. Check it out!
Here are the docs for the other packages as well.

Setting up the environment

We will be creating a REST API that will be consumed by a SPA. Web authentication will be used for signing into the SPA and it will pull data via authenticated API calls.
This code is also hosted on github. Make sure to use the Main branch.
Go ahead and install a tenancy compatible database (MySQL, MariaDB, or Postgre) and get PHP and a web server working. (Not covering that here).

Install Laravel and packages


composer create-project --prefer-dist laravel/laravel laravel-tenancy-passport-demo "5.6.*"
composer require "hyn/multi-tenant:5.2.*"
composer require laravel/passport


The hyn/multi-tenant package won’t auto-discover unless the ‘system’ database driver is present. Just copy a connection that is there in config/database.phpand rename it system. I used the mysql connection.


Publish necessary configuration files and scaffolding

Add this line to your App\Providers\AppServiceProvider.php


Then run the following commands to publish the necessary configuration files and migrations.


Setup environment variables

Go ahead and set the environment variables to match your setup. Make sure that DB_CONNECTION is set to system. Also note the URLs specified. The APP_URL will be the URL for the landing page and the TENANT_BASE_URL is the base URL for the tenant websites. For MySQL users, copy over the LIMIT_UUID_LENGTH_32=1.


Setting up Tenancy

Tenancy config

Open up config/tenancy.php. There are a few changes we need to make.


The abort-without-identified-hostname option will allow us to setup a landing page. The landing page won’t be associated with a tenant and would give us a 404 error when we browse to www.itplog.com. The update-app-url option will update email/notification URLs in Laravel internally so they are the tenant FQDN instead of APP_URL in the .env file

Enforce Tenant Middleware

I grabbed this from Ashok’s article I linked above. This helps will making Third-Party packages tenant aware.


Then we need to add the middleware ‘tenancy.enforce’ definition to our App\Http\Kernel.php file as shown below.


Tenant Migrations

Create a folder called tenant in database/migrations. Move all of the migrations files except for the ones that have ‘tenancy’ in the name.


All of the migrations in the tenant folder with be migrated to the tenant databases. System migrations will be in the migrations folder.
You can now run php artisan migrate to migrate the system database.
Make sure the database has been created or you will get an error!

UsesTenantConnection Trait

This trait needs to be added to all of the models that live in the tenant databases. See my user model below.


UsesSystemConnection Trait

This trait needs to be added to all models that are supposed to be in the system database.

Setting up Passport… Tenant Style

Now we are on to setting up passport. This is were I spent most of my time when trying to get all of this to work. The problem will Passport is the models it uses aren’t resolved through the Laravel IoC. This makes it difficult because the implementations can’t be easily overridden without extending the classes or modifying the vendor files. This is were the EnforceTenancy middleware comes in.

Passport Routes and Token Settings

Open up App\Providers\AuthServiceProvider.php and add the passport routes as shown below. Notice how we are able to specify the middleware to enforce a tenant connection. The rest of the settings allow us to call passport:install from a controller and shorten the lifetime of the passport tokens.
As pointed out in the comments by Saji, the token expire lifetimes setup here don’t apply to Personal Access Tokens that Passport uses with the createFreshApiToken middleware. The lifetime of the user session will actually be controller by the session lifetime configured in config(session.lifetime).


hasApiTokens Trait

Open up App\User.php and add the hasApiTokens trait.


This will automatically add a cookie with the Personal Access Token once the user logs in. The user will be able to seamlessly access our Passport protected API!

Modify Kernel.php again…

Go ahead and add the CreateFreshApiToken:class to the web middleware group as shown below.


Modify config/auth.php

Change the api.driver setting to passport as shown below


That is it for Passport! Now we just add the auth:api middleware to any API route we want to protect.

Adding Routes

In my app I needed to split up the authentication routes. This is because some were tenant specific and others weren’t. I also setup routing for the landing page using a domain route group with a catchall at the end. This way tenant URLs won’t work with /register and any other landing page specific routes.


Modifying the Auth Controllers

At this point you should be able to view the registration and login pages, but they won’t work. This is because they are not creating tenants upon registration. We can take care of that with a few modifications.

Creating Tenants

First lets create a class that handles tenant creation. Most of the functionality is from Ashok’s class, and I modified it to for my needs.


Now when we register a user we can call the create static method to create a save a new tenant. The app(Environment::class)->tenant($website) line is very important because it actually changes the database connection to tenant. With that all database calls for this request will save to the tenant specific database.

Modifying the Register Controller

With the Register Controller we to override the register method and add the logic to validate a tenant URL and create the tenant. We will then create the user in the new tenant database and redirect to the tenant specific login page.


Adding the Ticket Model and Resource Controller

Now we can create the files required to power the Ticket model. Run the below commands to generate:


Edit Migration

Move the ticket migration file to the database/migrations/tenant folder then add the following lines to it.


Add API Route

Now add this line in your api.php and make sure you are applying the auth:api middleware.


Controllers, Resources, Requests, Model, etc

I am using API Resources to return the data. I think it makes the controllers a little cleaner.

Ticket Model

Make sure you add the guarded property to the Model. Otherwise you can add $fillable and write out the attributes.


Ticket Controller



Ticket Request



Ticket Resource



Modify Auth Views for Tenancy

We can now make a few modification to the default Auth views. I will be overriding these later, but we will at least be able to test everything up till this point.

register.blade.php

Add the below field to the form. This will allow us to specify a FQDN during registration.


welcome.blade.php and app.blade.php

In these views we just need to comment out the login links. These won’t work from the www.itplog.com domain and will result in a 404 error.


Wrapping up

Whoa! That’s a lot of code. We accomplished everything concerning the back-end of this application. The API should be functional at this point, but logging in doesn’t really do anything yet.
In the next article, I will write some tenant-aware tests, overwrite the default front-end, and create a small SPA using VueJS.
All of the code is available at the github repo I posted at the beginning of this article.
See ya!

Post a Comment

3 Comments

Thanks for comment.