Laravel API — Authenticate user with custom driver & different table using auth middleware
Laravel is a good web framework. (I am not sure. People say so. It’s the only framework I know and I do for living). It comes with almost everything you need as well as simple to extend the functionality you need.
Laravel Middleware — Web & API
If we just have a look at Laravel’s App\Providers\RouteServiceProvider
class, we will see Laravel binds web
middleware to web routes to routes/web.php
& api
middleware to routes/api.php
. Web middleware provides session
& cookie
— Check THIS. Where API middleware provides throttling
— Check THIS.
For both the web
& api
, Laravel provides an Authentication
Middleware. If you say a group of routes should be available only for guest users & another group for logged in users then your can define routes like below.
<?php
// routes/web.php
Route::group(['middleware' => 'auth'], function(){
// AUTHENTICATED USER
// ENLIST routes here
});Route::group(['middleware' => 'guest'], function(){
// GUEST/UNAUTHORIZED USERS
// ENLIST routes here
});
This auth
is a middleware. Laravel uses some guards to validate or invalidate the user. By default, Laravel ships with two guards. web
& api
guards. If you use auth
middleware & you don’t define what guards should be used, Laravel by default takes the web
guard.
How to define the GUARD?
To define a guard, you have to use :
sign with auth
middleware. Like auth:web
or auth:api
.
<?php
// routes/api.php
Route::group(['middleware' => 'auth:api'], function(){
// AUTHENTICATED users here,
// define routes here
});
I mentioned before that if you don’t define any guard, web
is the default guard. So, auth
and auth:web
are same. For the web
guard, Laravel uses session, cookie. For api
guard, Laravel tries to look for api_token inside the header, form body, query string. If it fails to find the api_token then the user is not authenticated. If it finds the api_token then it tries to match the api token with database. If it can find any match then the user is authenticated otherwise he is NOT.
So, What’s the problem here?
Okay. Let’s check how Laravel stores the access token. By default, to use api
guard with auth
middleware, you will have to save the user’s access_token inside the users
tables under a column name api_token. Something like,
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
// the following column not available in my schema
$table->string('api_token'); // <===
$table->timestamps();
});
}
But, I want to save multiple access_tokens. Which is not possible in this database schema. To achieve this, we have to create another table. May be named as tokens
. Something like this.
public function up () {
Schema::create('tokens', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('access_token');
$table->string('refresh_token');
$table->dateTime('expires_in');
$table->timestamps();
});
}
So, whenever a user is registered or logged in, issue an access_token and save it to tokens
table with the user_id
. User can create as many as they want. Can login from multiple devices.
But, if we try to follow this, we can’t use Laravel’s auth:api
middleware. It will throw an exception that the user is not logged in. Because for our schema, we don’t have the api_token
field in our users
table. So, how can we use the auth:api
or something like this so that we can use the Auth
facade from anywhere.
To overcome this issue, we have to define our own guard, so that we can pass it to auth
middleware.
Define a GUARD
Laravel’s Documentation says, to add a custom guard you’ve to use Auth::extend
method which will return an implementation of Illuminate\Contracts\Auth\Guard
& can be placed in any ServiceProvider registered. As Laravel ships with AuthenticationServiceProvider
it’s good to use this within that class.
The access_token will be the driver for the guard we are going to define next. It can be anything. You can name it anything. As of the documentation, AccessTokenGuard
is the implementation of Guard
interface.
Within the AccessTokenGuard
class we need to implement the unimplemented methods of Guard
interface. Laravel’s default TokenGuard
& SessionGuard
classes uses GuardHelpers
trait. So, we can implement that. And the GuardHelpers
doesn’t ship two methods user
and validate
. So, we need to declare those methods inside our Guard class.
As we implemented the GuardHelpers
, if we look into it we will find that it requires an implementation of Illuminate\Contracts\Auth\UserProvider
interface. So we need to create a new class to work with it. So the class is below.
What does the UserProvider
contract do? It’s discussed in Laravel’s documentation.
The
retrieveById
function typically receives a key representing the user, such as an auto-incrementing ID from a MySQL database. TheAuthenticatable
implementation matching the ID should be retrieved and returned by the method.The
retrieveByToken
function retrieves a user by their unique$identifier
and "remember me"$token
, stored in a fieldremember_token
. As with the previous method, theAuthenticatable
implementation should be returned.The
updateRememberToken
method updates the$user
fieldremember_token
with the new$token
. The new token can be either a fresh token, assigned on a successful "remember me" login attempt, or when the user is logging out.The
retrieveByCredentials
method receives the array of credentials passed to theAuth::attempt
method when attempting to sign into an application. The method should then "query" the underlying persistent storage for the user matching those credentials. Typically, this method will run a query with a "where" condition on$credentials['username']
. The method should then return an implementation ofAuthenticatable
. This method should not attempt to do any password validation or authentication.The
validateCredentials
method should compare the given$user
with the$credentials
to authenticate the user. For example, this method should probably useHash::check
to compare the value of$user->getAuthPassword()
to the value of$credentials['password']
. This method should returntrue
orfalse
indicating on whether the password is valid.
After implementing these things, we are almost done. Now we have to define this to our config/auth.php
that you should use this guard to validate a user.
<?php
// config/auth.php
return [
// ...
'guards' => [
// ...
// ...
// this will be like auth:token instead of auth:api
// name the guard anything you want
'token' => [
// access_token is what we defined inside Auth::extend
// you can name this anything BUT should match with
// Auth::extend('HERE');
'driver' => 'access_token',
],];
That’s it. Now you can define your routes like below and can use the Auth
facade with the full functionality except the login & logout. Even the auth:api
doesn’t provide login & logout. Routes can be like,
<?php
// routes/api.php
// token is the guard name, we defined above in config/auth.php
Route::group(['middleware' => 'auth:token'], function(){
// AUTHENTICATED users here,
// define routes here
});
That’s it. I have created a github repository for this. Please have a look as a whole. Laravel Custom Auth. There are some routes define. Postman collection added on public/postman-data with postman environment. Some test cases are defined too to check the accuracy with the auth:token
and auth:api
. You can clone it & play with it.