Open-source project
robsontenorio/laravel-keycloak-guard avatar
robsontenorio/laravel-keycloak-guard

laravel-keycloak-guard authenticates a Laravel API against Keycloak JWTs

🔑 Simple Keycloak Guard for Laravel

522 stars163 forksPHPMIT

At a glance

What is it?
laravel-keycloak-guard is a Laravel auth guard that accepts JWT tokens issued by a Keycloak server, verifies the signature, structure, expiry and resource access, then optionally loads or syncs the matching user from your database. It is MIT licensed, installs with one Composer command, and the README states plainly that if your setup does not match its assumptions you want a different package.
Who is it for?
laravel-keycloak-guard suits a Laravel API whose users already authenticate against Keycloak directly and keep the resulting token, since the package does only the server-side half of that flow and leaves the frontend untouched.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What it does, and the assumption it makes

This package helps you authenticate users on a Laravel API based on JWT tokens generated from Keycloak Server.

That is a narrow description on purpose, and the README spends most of its space on the assumptions behind it. The requirements are stated as a checklist.

You are building an API with Laravel. You will not use Laravel Passport for authentication, because Keycloak Server will do the job. The frontend is a separated project. The frontend users authenticate directly on Keycloak Server to obtain a JWT token, and this process has nothing to do with the Laravel API. The frontend keeps the JWT token from Keycloak Server. And the frontend makes requests to the Laravel API with that token.

Then the last line is the one to notice. If your app does not match requirements, probably you are looking for the Socialite provider for Keycloak or the Vizir laravel-keycloak-web-guard package.

So the package deliberately does one thing. The login form lives in Keycloak, the frontend obtains the token, and the frontend sends that token to your API. The API's only job is to validate it and turn it into an authenticated user. There is no callback, no session, no redirect and no user record created at login time.

That narrowness is a feature if you already run Keycloak, because the alternative is reimplementing the parts of an identity provider that you would otherwise be running anyway. It is a limitation if you wanted Keycloak to be one login option among several, which is exactly what the two alternative packages are for.

The primary language is PHP, the licence is MIT, and the last push was on 2026-04-17, with the release 2.0.1 on the same day, 2.0.0 in March 2026 and 1.6.3 in December 2024.

The verification steps, in order

The flow section of the README describes what happens when a protected endpoint receives a request.

The frontend user authenticates on Keycloak Server, obtains a JWT token, and at another moment makes a request to some protected endpoint on a Laravel API with that token. The Laravel API, through the Keycloak Guard, handles it, and the guard does four things.

Verify token signature. Verify token structure. Verify token expiration time. And verify if my API allows resource access from token.

If everything is ok, then find the user on database and authenticate it on my API. Optionally, the user can be created or updated in the API users database. And then the response is returned.

The order is the point. Signature first, because a token whose signature does not verify is not a token at all. Structure second, because a valid signature over a malformed payload still tells you nothing. Expiry third. And resource access last, because that is the one check that is specific to your API rather than to Keycloak, since it is the check that decides whether a token minted for a different client should be accepted here.

The optional user step is where the package gets opinionated, and it is covered in detail in the configuration section. By default the guard loads the user from the database and fills values into the authenticated user object, working together with the user_provider_credential and token_principal_attribute settings. If you have no users table at all, you disable that with load_user_from_database and use the user object provided from the JWT token instead. If you have a users table and want it kept up to date from the token, you point at a custom method on a custom UserProvider, which receives the complete decoded token rather than just the credentials, and which lets you decide how the database is touched before the authenticated user object is delivered.

That custom method receives the parameters $token, an object, and $credentials, an associative array, and it requires load_user_from_database to be true. When you use it, the user_provider_credential and token_principal_attribute values are ignored.

Installing it and wiring up the guard

Installation is one Composer command:

bash
composer require robsontenorio/laravel-keycloak-guard

If you are using Lumen, register the provider in your bootstrap app file, and for facades uncomment $app->withFacades() in the same file:

php
$app->register(\KeycloakGuard\KeycloakGuardServiceProvider::class);

Then you publish the config file:

bash
php artisan vendor:publish  --provider="KeycloakGuard\KeycloakGuardServiceProvider"

The environment variables are short, and each one maps to a configuration option:

.env
KEYCLOAK_REALM_PUBLIC_KEY=MIIBIj...         # Get it on Keycloak admin web console.
KEYCLOAK_LOAD_USER_FROM_DATABASE=false      # You can opt to not load user from database, and use that one provided from JWT token.
KEYCLOAK_APPEND_DECODED_TOKEN=true          # Append the token info to user object.
KEYCLOAK_ALLOWED_RESOURCES=my-api           # The JWT token must contain this resource `my-api`.
KEYCLOAK_LEEWAY=60                          # Optional, but solve some weird issues with timestamps from JWT token.

There is a warning attached to the whole block: when editing .env make sure all strings are trimmed.

The Keycloak public key is the one piece you have to fetch from Keycloak yourself. The README describes the path: open the Keycloak security settings in the admin console, then Keys, then the line for the algorithm RS256, or whatever is defined under the token_encryption_algorithm configuration, then the Public Key button.

The auth guard then goes into config/auth.php, and the change is small:

php
'defaults' => [
    'guard' => 'api',                 # <-- This
    'passwords' => 'users',
],
'guards' => [
    'api' => [
        'driver' => 'keycloak',       # <-- This
        'provider' => 'users',
    ],
],

Finally the routes, and here the README's tone is justified by the simplicity of the result. Protect some endpoints on routes/api.php and you are done:

php
// public endpoints
Route::get('/hello', function () {
    return ':)';
});

// protected endpoints
Route::group(['middleware' => 'auth:api'], function () {
    Route::get('/protected-endpoint', 'SecretController@index');

    // more endpoints ...
});

The configuration options that need judgement

Most of the options are mechanical, but four of them are decisions you have to make rather than values you copy.

KEYCLOAK_REALM_PUBLIC_KEY is required, and it is the Keycloak Server public key as a string. token_encryption_algorithm defaults to RS256, and it is the algorithm the token is signed with.

load_user_from_database defaults to true and is required. If you do not have a users table you must disable it, in which case the user object comes from the JWT token itself.

user_provider_credential defaults to username and is required. It is the field in the users table that holds the unique identifier, a username or email or nickname, and it is confronted against the token_principal_attribute while authenticating. token_principal_attribute defaults to preferred_username and is required, and it is the property in the JWT token that carries that identifier. Both sides of that comparison are configurable, which is what lets the package work with a users table that identifies people by something other than the Keycloak preferred name.

allowed_resources is required, and it is the resource access the token must contain. Usually your API should handle one resource access, but if you handle multiple you can use a comma separated list. The attribute is confronted against the resource_access attribute from the JWT token while authenticating. The override is ignore_resources_validation, which defaults to false and disables resource validation entirely, ignoring allowed_resources. That switch exists for the case where you use one Keycloak client across several APIs and cannot separate the resources, and it removes the check that stops a token minted for another service being accepted by yours.

append_decoded_token defaults to false. When enabled it appends the full decoded JWT token to the authenticated user as $user->token, which is useful if you need the roles, groups and other user information the token carries. The option is a convenience rather than a requirement, because even with it set to false you can get the same token using Auth::token().

And leeway defaults to 0. The README describes it as optional but useful, since it solves some weird issues with timestamps from JWT tokens. Leeway is the number of seconds of clock skew you tolerate on the expiry check, and the default of 60 in the example is a sensible setting for a deployment where the API server and the Keycloak server keep slightly different times.

Where the package fits, and where it stops

The comparison worth making is with the two alternatives the README names, and with writing the guard yourself.

Against the Socialite Keycloak provider, the difference is where the authentication happens. Socialite is an OAuth login flow initiated from your Laravel app, so Laravel owns the login screen and the callback. This package assumes the user has already logged in on Keycloak and arrives with a token. If your users log in through your own application, you need the Socialite route; if they log in on Keycloak and your frontend holds the token, you need this one.

Against the Vizir laravel-keycloak-web-guard, which is a web guard rather than an API guard, the difference is the same axis: this package has no session and no cookie, so it is for stateless API traffic where the client is a separate frontend holding a token.

Against writing it yourself, the value is the four verification steps and, more importantly, the resource access check. A hand-written guard that verifies the signature and the expiry will accept a token issued to a different Keycloak client for a different service, and the resource_access comparison is what stops that. That check is the one people skip and the one that matters.

The limits are worth stating. This package assumes a very specific architecture, and its own README tells you to use something else otherwise. It has no concept of Keycloak clients, of refresh tokens, of token revocation or of logout propagation, because none of that is in the flow it implements; if the token is expired, the client has to go back to Keycloak. The user loading is either a database lookup by one field, or a single custom callback, so a user record with several identifiers to reconcile is more work than the package does for you. And there is no session state, so every request carries the full token.

The maintenance picture is healthy for a small package. The releases are 2.0.1 from 2026-04-17, 2.0.0 from 2026-03-23 and 1.6.3 from 2024-12-28, so the 2.x line is a recent rewrite rather than an abandoned project. The repository has a phpunit.xml.dist, a tests/ directory, a .php-cs-fixer.php configuration for style, and a codecov badge, so the package is tested and its formatting is enforced automatically.

One practical note for anyone evaluating it. The .env trimming warning is not boilerplate. The values are compared as strings during configuration, so a trailing space in the Keycloak public key produces a signature verification failure that looks like a Keycloak problem rather than a configuration one.

Editorial conclusion

laravel-keycloak-guard suits a Laravel API whose users already authenticate against Keycloak directly and keep the resulting token, since the package does only the server-side half of that flow and leaves the frontend untouched. It is the wrong tool if your Laravel app is also the login form, because the frontend is expected to be a separate project that authenticates on Keycloak itself, and it is the wrong tool if you want Keycloak social login through Laravel rather than direct token issuance. Before you install it, get the Keycloak public key from the Keycloak admin console and set KEYCLOAK_ALLOWED_RESOURCES to the resource your API actually serves, and trim every value you put in the .env file, since the README warns that untrimmed strings break the configuration.

Frequently asked questions

What does the laravel-keycloak-guard package do?

It helps you authenticate users on a Laravel API based on JWT tokens generated from a Keycloak server. On each protected request the guard verifies the token signature, the token structure, the expiration time and whether your API allows the resource access claimed in the token, then finds the user in the database and authenticates them.

What are the requirements for using laravel-keycloak-guard?

You are building a Laravel API, you are not using Laravel Passport, your frontend is a separate project, and your frontend users authenticate directly on the Keycloak server to obtain a JWT token that they then send to the API. If your app does not match that, the README suggests the Socialite Keycloak provider or the Vizir laravel-keycloak-web-guard instead.

How do I install and configure the guard?

Run composer require robsontenorio/laravel-keycloak-guard, publish the config with php artisan vendor:publish --provider="KeycloakGuard\KeycloakGuardServiceProvider", set the Keycloak public key and the other values in .env, then set the default guard and the guards entry in config/auth.php to use the keycloak driver, and protect endpoints with the auth:api middleware.

What does KEYCLOAK_ALLOWED_RESOURCES do?

It is the resource access the JWT token must contain, and it is confronted against the resource_access attribute from the token while authenticating. Usually an API handles one resource access, but a comma separated list is accepted for several. Setting ignore_resources_validation to true disables the check entirely.

Can I create or update users in my database from the token?

Yes. Set load_user_from_database to true and point user_provider_custom_retrieve_method at a method on your own UserProvider, which is called instead of retrieveByCredentials and receives the complete decoded token along with the credentials, so you control how the database is touched before the authenticated user object is delivered.

What is the leeway setting for?

It defaults to 0 and lets you add a tolerance to the expiration check, which the README describes as optional but useful because it solves some weird issues with timestamps from JWT tokens. The example configuration sets KEYCLOAK_LEEWAY=60 to tolerate a minute of clock skew between the API server and Keycloak.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. robsontenorio/laravel-keycloak-guard on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/robsontenorio-laravel-keycloak-guard.svg)](https://hysenlabs.com/projects/robsontenorio-laravel-keycloak-guard)

Was this useful?