abublihi / laravel-external-jwt-guard
It's a laravel custom auth guard for authenticating users using External JWT
Installs: 1 094
Dependents: 0
Suggesters: 0
Security: 0
Stars: 13
Watchers: 2
Forks: 1
Open Issues: 0
Requires
- php: ^8.0
- illuminate/support: ^8.0|^9.0|^10.0|^11.0
- lcobucci/clock: ^2.0|^3.0
- lcobucci/jwt: ^4.2|^5.0
Requires (Dev)
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0
- phpunit/phpunit: ^9.0|^10.0
README
This package provides a simple custom authentication guard for Laravel using an external JWT provided by an OAuth server or Any type of SSO that uses a JWT. Below a figure describe the flow.
Installation
You can install the package via composer:
composer require abublihi/laravel-external-jwt-guard
publish the configuration file externaljwtguard.php
php artisan vendor:publish --provider="Abublihi\LaravelExternalJwtGuard\LaravelExternalJwtGuardServiceProvider" --tag config
Usage
The package is very simple but also powerful when it comes to customization, After installation and publishing of the configurations you should first configure your default
authorization server,
NOTE: The package allow you to add multiple authorization servers but for mostly use cases you only need one authorization server.
Configure your Authorization server
<?php return [ 'authorization_servers' => [ 'default' => [ /* Identification settings */ 'id_claim' => env('JWT_GUARD_ID_CLAIM', 'sub'), 'roles_claim' => env('JWT_GUARD_ROLES_CLAIM', 'roles'), 'id_attribute' => env('JWT_GUARD_ID_ATTRIBUTE', 'id'), /* Creation setting */ 'create_user' => env('JWT_GUARD_CREATE_USER', false), 'create_user_action_class' => null, /* Validation settings */ 'issuer' => '', 'validate_issuer' => true, 'public_key' => env('JWT_GUARD_AUTH_SERVER_PUBLIC_KEY'), // if RSA, make sure it's start with -----BEGIN PUBLIC KEY----- and ends with -----END PUBLIC KEY----- 'signing_algorithm' => env('JWT_GUARD_AUTH_SIGN_ALG', 'RS256'), ], // you could add as many as you want of the authorization servers by duplicating the configurations above ^^ 'admin' => [ 'id_claim' => 'sub', ..... ] ], ];
please head to configuration file config/externaljwtguard.php
, the configurations is separated in three main Sections:
- Identification settings
- Creation setting (optional)
- Validation settings
Identification settings
First will go over the configuration of Identification settings
, as the name denotes, the Identification settings
is the configurations that allows the package to identify the user by using the JWT claims.
NOTE: please make sure these are configured well.
NOTE: id_attribute is in your system, the package use it to identify the authenticated user for example if you have configured the guard to a provider that is configured to a User model the package will look for the id_attribute and match it with the id_claim from the JWT
'id_claim' => env('JWT_GUARD_ID_CLAIM', 'sub'), 'roles_claim' => env('JWT_GUARD_ROLES_CLAIM', 'roles'), // not yet used 'id_attribute' => env('JWT_GUARD_ID_ATTRIBUTE', 'id'), // in your database (e.g. users table)
Creation setting (optional)
The creation setting is used to configure how will create a user if not exists in the system, you can disable this feature and we encourage disabling it.
NOTE: You have to make your own action to create the user that should implement the interface
Abublihi\LaravelExternalJwtGuard\Interfaces\CreateUserActionInterface
'create_user' => env('JWT_GUARD_CREATE_USER', false), // you can define your own action by implementing the interface Abublihi\LaravelExternalJwtGuard\Interfaces\CreateUserActionInterface 'create_user_action_class' => null,
Validation settings
'issuer' => 'https://example.com', 'validate_issuer' => true, 'public_key' => env('JWT_GUARD_AUTH_SERVER_PUBLIC_KEY'), // if RSA, make sure it's start with -----BEGIN PUBLIC KEY----- and ends with -----END PUBLIC KEY----- 'signing_algorithm' => env('JWT_GUARD_AUTH_SIGN_ALG', 'RS256'),
Guard Configuration
After we have configured our Authorization server next we have to configure the our guard in config/auth.php
in the Guards you can add/modify the guards where you want to use JWT as authentication guard by setting the driver to external-jwt-auth
. We have a custom attribute which is auth_server_key
that indcates the authroization server key, it's by default set to default
.
'guards' => [ . . 'api-jwt' => [ 'driver' => 'external-jwt-auth', // <-- here you have to set the drive to `external-jwt-auth` 'provider' => 'users', ], // you can set the authorization server key as seen below 'api-jwt-admin' => [ 'driver' => 'external-jwt-auth', // <-- here you have to set the drive to `external-jwt-auth` 'provider' => 'users', 'auth_server_key' => 'admin', // the authorization key for admin ], . . ],
Test your configuration
Add a route in for example routes/api.php
Route::middleware('auth:api-jwt')->group(function() { Route::get('user', function(){ return request()->user(); // <-- will return the user which is configured }); });
JWT role middleware
The package also comes with a role middleware that checks the roles of the JWT (User), you should configure it right first by using the config file roles_claim
to the right roles claim which should be an array of roles. to use the middleware you have two options:
- define an alias in
app/Http/Kernel.php
- use it directly without an alias
Defining the middleware Alias in the kernel
Go to app/Http/Kernel.php
and add the following line
NOTE: The name of the alias could be any thing
protected $middlewareAliases = [ // ... 'jwt-role' => \Abublihi\LaravelExternalJwtGuard\Middleware\CheckJwtRoles::class ];
Using the middleware in the routes
Route::group(['middleware' => ['auth:api-jwt' 'jwt-role:manager']], function () { // this will allow any jwt with the role `manager` });
You can specify multiple roles with a | (pipe) character, which is treated as OR
Route::group(['middleware' => ['auth:api-jwt' 'jwt-role:manager|super-admin']], function () { // this will allow any jwt with the role `manager` or `super-admin` });
Using the Middleware directly without defining it on the kernel
use Abublihi\LaravelExternalJwtGuard\Middleware\CheckJwtRoles; Route::group(['middleware' => ['auth:api-jwt' CheckJwtRoles::class.':manager']], function () { // this will allow any jwt with the role `manager` });
You can specify multiple roles with a | (pipe) character, which is treated as OR
// with OR operator Route::group(['middleware' => ['auth:api-jwt' CheckJwtRoles::class.':manager|super-admin']], function () { // this will allow any jwt with the role `manager` });
Example JWT with roles claim
{ "iss": "http://example.com", "aud": "http://example.org", "sub": "2", "jti": "4f1g23a12aa", "iat": 1707071173.863238, "nbf": 1707071113.863238, "exp": 1707074773.863238, "uid": "2", "roles": [ "manager", "super-admin" ] }
Testing
ActingAs Functionality
This package provide actingAs function which help you to test you routes that are protected by the external-jwt-auth
guard, To use it simply use the \Abublihi\LaravelExternalJwtGuard\Traits\ActingAs
trait in your test, then call actingAsExternalJwt
function, this will generate a configiruation (private and public key) and a vaild token then add it to the request headers using $this->withHeader(..)
class SampleTest extends TestCase { use DatabaseMigrations, \Abublihi\LaravelExternalJwtGuard\Traits\ActingAs; /** * @test * @define-route usesAuthRoutes */ function test_it_returns_authenticated_user_by_jwt() { $user = User::factory()->create(); $this->actingAsExternalJwt($user); } }
if you want to generate a token with some custom claims or as invaild or as expired, you can pass an instance of Abublihi\LaravelExternalJwtGuard\Support\FakeTokenIssuer
to actingAsExternalJwt
if you want to generate a token which is invaild or expired for example you can pass an instance of Abublihi\LaravelExternalJwtGuard\Support\FakeTokenIssuer
to actingAsExternalJwt
use Abublihi\LaravelExternalJwtGuard\Support\FakeTokenIssuer; class SampleTest extends TestCase { use DatabaseMigrations, \Abublihi\LaravelExternalJwtGuard\Traits\ActingAs; /** * @test * @define-route usesAuthRoutes */ function test_it_returns_authenticated_when_with_role_admin() { $user = User::factory()->create(); $this->actingAsExternalJwt( FakeTokenIssuer:user($user) ->withClaims([ 'roles' => [ 'admin' ] ]) ); } /** * @test * @define-route usesAuthRoutes */ function test_it_returns_authenticated_when_with_custom_claim_employee_name() { $user = User::factory()->create(); $this->actingAsExternalJwt( FakeTokenIssuer:user($user) ->withClaims([ 'employee_name' => 'Mohammed Abdullah', 'other_info' => [ 'info 1', 'info 2' ] ]) ); } /** * @test * @define-route usesAuthRoutes */ function test_it_returns_unauthenticated_when_invaild() { $user = User::factory()->create(); $this->actingAsExternalJwt( FakeTokenIssuer:user($user) ->asInvalid() ); } /** * @test * @define-route usesAuthRoutes */ function test_it_returns_unauthenticated_when_expired() { $user = User::factory()->create(); $this->actingAsExternalJwt( FakeTokenIssuer:user($user) ->asExpired() ); } }
The generated code and configuration will be set to the default
authorization server, you can change it by passing the authorization server key to the second param.
class SampleTest extends TestCase { use DatabaseMigrations, \Abublihi\LaravelExternalJwtGuard\Traits\ActingAs; /** * @test * @define-route usesAuthRoutes */ function test_it_returns_authenticated_user_by_jwt() { $user = User::factory()->create(); $this->actingAsExternalJwt($user, 'admin'); // this will set the authorization server key to `admin` } }
Testin the package
composer test
Changelog
Please see CHANGELOG for more information what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email abublihi@gmail.com instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.