This package helps you authenticate users on a Laravel API based on JWT tokens generated from Keycloak Server.
✔️ I`m building an API with Laravel.
✔️ I will not use Laravel Passport for authentication, because Keycloak Server will do the job.
✔️ I already have an "users table", with unique identifiers, on my database.
✔️ The frontend is a separated project.
✔️ The frontend users authenticate directly on Keycloak Server to obtain a JWT token. This process have nothing to do with the Laravel API.
✔️ The frontend keep the JWT token from Keycloak Server.
✔️ The frontend make requests to the Laravel API, with that token.
-
The frontend user authenticates on Keycloak Server
-
The frontend user obtains a JWT token.
-
In another moment, the frontend user makes a request to some protected endpoint on a Laravel API, with that token.
-
The Laravel API (through
Keycloak Guard
) handle it.- Verify token signature.
- Verify token structure.
- Verify token expiration time.
- Verify if my API allows
resource access
from token.
-
If everything is ok, find the user on database and authenticate it on my API.
-
Return response
Require the package
composer require robsontenorio/laravel-keycloak-guard
Publish the config file
php artisan vendor:publish --provider="KeycloakGuard\KeycloakGuardServiceProvider"
The Keycloak Guard configuration can be handled from Laravel .env
file.
<?php
return [
'realm_public_key' => env('KEYCLOAK_REALM_PUBLIC_KEY', null),
'user_provider_credential' => env('KEYCLOAK_USER_PROVIDER_CREDENTIAL', 'username'),
'token_principal_attribute' => env('KEYCLOAK_TOKEN_PRINCIPAL_ATTRIBUTE', 'preferred_username'),
'append_decoded_token' => env('KEYCLOAK_APPEND_DECODED_TOKEN', false),
'allowed_resources' => env('KEYCLOAK_ALLOWED_RESOURCES', null)
];
✔️ realm_public_key
Required.
The Keycloak Server realm public key (string).
✔️ user_provider_credential
Required. Default is username
.
Any field from "users" table that contains the user unique identifier (eg. username, email, nickname). This will be confronted against token_principal_attribute
attribute, while authenticating.
✔️ token_principal_attribute
Required. Default is preferred_username
.
The property from JWT token that contains the user identifier.
This will be confronted against user_provider_credential
attribute, while authenticating.
✔️ append_decoded_token
Default is false
.
Appends to the authenticated user the full decoded JWT token. Useful if you need to know roles, groups and another user info holded by JWT token. Even choosing false
, you can also get it using Auth::token()
, see API section.
✔️ allowed_resources
Required
Usually you API should handle one resource_access. But, if you handle multiples, just use a comma separated list of allowed resources accepted by API. This attribute will be confronted against resource_access
attribute from JWT token, while authenticating.
Changes on config/auth.php
'defaults' => [
'guard' => 'api', # <-- For sure, i`m building an API
'passwords' => 'users',
],
'guards' => [
'api' => [
'driver' => 'keycloak', # <-- Set the API guard driver to "keycloak"
'provider' => 'users',
],
],
Just protect some endpoints on routes/api.php
and you are done!
// public endpoints
Route::get('/hello', function () {
return ':)';
});
// protected endpoints
Route::group(['middleware' => 'auth:api'], function () {
Route::get('/protected-endpoint', 'SecretController@index');
// more endpoints ...
});
Simple Keycloak Guard implements Illuminate\Contracts\Auth\Guard
. So, all Laravel default methods will be available. Ex: Auth::user()
returns the authenticated user.
- check()
- guest()
- user()
- id()
- validate()
- setUser()
- token()
Ex: Auth::token()
returns full decoded JWT token from authenticated user
- hasRole('some-resource', 'some-role'): Check if the authenticated user has especific role into a resource.
Ex: Whit this payload:
'resource_access' => [
'myapp-backend' => [
'roles' => [
'myapp-backend-role1',
'myapp-backend-role2'
]
],
'myapp-frontend' => [
'roles' => [
'myapp-frontend-role1',
'myapp-frontend-role2'
]
]
]
Auth::hasRole('myapp-backend', 'myapp-backend-role1') => true
Auth::hasRole('myapp-frontend', 'myapp-frontend-role1') => true
Auth::hasRole('myapp-backend', 'myapp-frontend-role1') => false
Twitter @robsontenorio