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.php
and 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 fileEnforce 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!
3 Comments
How to convert text to speech using PHP
ReplyDeleteHow to create a multiple choice quiz in PHP and MySQL
Python Convert XML to CSV
Python 3 Tkinter Menu Bar
Python Tkinter Text Widget
Python Tkinter Tutorial with Examples
ReplyDeleteSQL Injection Prevention Techniques
Django upload image to database
Django ajax GET and POST request
PHP Sending HTML form data to an Email
PHP CURL Cookie Jar
PHP Server Side Form Validation
ReplyDeleteContour Detection using Python OpenCV
HTML Table Alternate Row Color using PHP
PHP User Authentication by IP Address
Python Pandas CSV to Dataframe
Simple way to send SMTP mail using Node.js
PHP Graphics
PHP count average word length and characters in a text file
Lemmatization nltk
Thanks for comment.