Laravel Passport Multi-Auth
Add multi-authentication support to Laravel Passport
Compatibility
Laravel Passport |
---|
^2.0 |
^3.0 |
Installing and configuring
- Install using composer:
composer require islem-kms/l5-mauth-pass
- If you are using a Laravel version less than 5.5 you need to add the provider on
config/app.php
:
'providers' => [
...
IslemKms\PassportMultiauth\Providers\MultiauthServiceProvider::class,
],
- Migrate database to create
oauth_access_token_providers
table:
php artisan migrate
- Add new provider in
config/auth.php
using a model that extends ofAuthenticatable
class and useHasApiTokens
trait.
Ex.:
- Configure your model:
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class Admin extends Authenticatable
{
use Notifiable, HasApiTokens;
- And your
config/auth.php
providers:
<?php
return [
...
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// ** New provider**
'admins' => [
'driver' => 'eloquent',
'model' => App\Administrator::class,
],
],
];
- Add a new
guard
inconfig/auth.php
guards array using driverpassport
and the provider added above:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
// ** New guard **
'admin' => [
'driver' => 'passport',
'provider' => 'admins',
],
]
- Register the middlewares
AddCustomProvider
andConfigAccessTokenCustomProvider
on$middlewareGroups
attribute onapp/Http/Kernel
.
class Kernel extends HttpKernel
{
...
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
\Barryvdh\Cors\HandleCors::class,
'custom-provider',
],
'custom-provider' => [
\IslemKms\PassportMultiauth\Http\Middleware\AddCustomProvider::class,
\IslemKms\PassportMultiauth\Http\Middleware\ConfigAccessTokenCustomProvider::class,
]
];
...
}
- Encapsulate the passport routes for access token with the registered middleware in
AuthServiceProvider
:
use Route;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
...
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
// Middleware `api` that contains the `custom-provider` middleware group defined on $middlewareGroups above
Route::group(['middleware' => 'api'], function () {
Passport::routes(function ($router) {
return $router->forAccessTokens();
});
});
}
...
}
Optional: Publish migrations:
php artisan vendor:publish
And choose the provider IslemKms\PassportMultiauth\Providers\MultiauthServiceProvider
Usage
- Add the
provider
parameter in your request at/oauth/token
:
POST /oauth/token HTTP/1.1
Host: localhost
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache
{
"username":"[email protected]",
"password":"password",
"grant_type" : "password",
"client_id": "client-id",
"client_secret" : "client-secret",
"provider" : "admins"
}
- You can pass your guards on
auth
middleware as you wish. Example:
Route::group(['middleware' => ['api', 'auth:admin']], function () {
Route::get('/admin', function ($request) {
// Get the logged admin instance
return $request->user(); // You can use too `$request->user('admin')` passing the guard.
});
});
The api
guard use is equals the example with admin
.
- You can pass many guards to
auth
middleware.
OBS: To pass many you need pass the api
guard on end of guards and the guard api
as parameter on $request->user()
method. Ex:
// `api` guard on end of guards separated by comma
Route::group(['middleware' => ['api', 'auth:admin,api']], function () {
Route::get('/admin', function ($request) {
// Passing `api` guard to `$request->user()` method
// The instance of user authenticated (Admin or User in this case) will be returned
return $request->user('api');
});
});
Refreshing tokens
- Add the
provider
parameter in your request at/oauth/token
:
POST /oauth/token HTTP/1.1
Host: localhost
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache
{
"grant_type" : "refresh_token",
"client_id": "client-id",
"client_secret" : "client-secret",
"refresh_token" : "refresh-token",
"provider" : "admins"
}
Using scopes
- If you are using more than one guard on same route, use the following package middlewares instead of using the
scope
andscopes
middlewares fromLaravel\Passport
.
protected $routeMiddleware = [
'multiauth.scope' => \IslemKms\PassportMultiauth\Http\Middleware\MultiAuthCheckForAnyScope::class,
'multiauth.scopes' => \IslemKms\PassportMultiauth\Http\Middleware\MultiAuthCheckScopes::class,
];
The middlewares are equals the Laravel\Passport
middlewares with guard api
on request->user()
object.
User Sample:
Route::group([
'middleware' => ['auth:admin,api', 'multiauth.scope:read-books']
], function ($request) {
return $request->user('api');
});
Unit tests
If you are using multi-authentication in a request you need to pass just an Authenticatable
object to Laravel\Passport\Passport::actingAs()
. E.g.:
- You have a route with multi-auth:
Route::group(['middleware' => 'auth:admin,api'], function () {
Route::get('/foo', function ($request) {
return $request->user('api'); // Return user or admin
});
});
- On your test just pass your entity to
Passport::actingAs()
:
use App\User;
use App\Admin;
use Laravel\Passport\Passport;
class MyTest extends TestCase
{
public function testFooAdmin()
{
$admin = factory(Admin::class)->create();
Passport::actingAs($admin);
// When you use your endpoint your admin will be returned
$this->json('GET', '/foo')
->assertStatus(200)
->assertJson([
'data' => [
'id' => 1,
'name' => 'Admin',
'email' => '[email protected]'
]
]);
}
public function testFooUser()
{
$user = factory(User::class)->create();
Passport::actingAs($user);
// When you use your endpoint your user will be returned
$this->json('GET', '/foo')
->assertStatus(200)
->assertJson([
'data' => [
'id' => 1,
'name' => 'User',
'email' => '[email protected]'
]
]);
}
}
- If your route has just one guard:
Route::group(['middleware' => 'auth:admin'], function () {
Route::get('/foo', function ($request) {
return $request->user(); // Return admin
});
});
- And on your tests just pass your entity, scopes and guard to
Passport::actingAs()
:
use App\User;
use App\Admin;
use Laravel\Passport\Passport;
class MyTest extends TestCase
{
public function testFooAdmin()
{
$admin = factory(Admin::class)->create();
Passport::actingAs($admin, [], 'admin');
// When you use your endpoint your admin will be returned
$this->json('GET', '/foo')
->assertStatus(200)
->assertJson([
'data' => [
'id' => 1,
'name' => 'Admin',
'email' => '[email protected]'
]
]);
}
}