acsystems/keycloak-guard-bundle
Laravel guard/authentication bundle for integrating Keycloak. Adds a custom auth guard, handles token validation and user resolution from Keycloak, and supports protecting routes with Keycloak-backed authentication for API or web apps.
Installation
composer require acsystems/keycloak-guard-bundle
Add to config/bundles.php:
return [
// ...
AcSystems\KeycloakGuardBundle\KeycloakGuardBundle::class => ['all' => true],
];
Configuration
Update config/packages/acsystems_keycloak_guard.yaml with your Keycloak realm details:
keycloak:
realm: 'your-realm'
auth_server_url: 'https://your-keycloak-server/auth'
resource: 'your-client-id'
public_key: '-----BEGIN PUBLIC KEY-----...' # From Keycloak admin console
First Use Case: JWT Authentication
Add the guard to security.yaml:
security:
firewalls:
main:
guard: keycloak
stateless: true
Verify
Test with a JWT token in the Authorization: Bearer <token> header. The guard will validate it against Keycloak’s public key.
Token Acquisition
Redirect users to Keycloak for login (e.g., /auth/realms/{realm}/protocol/openid-connect/auth).
After login, Keycloak redirects back with a code or token in the URL/fragment.
Token Validation Use the guard’s built-in validator:
use AcSystems\KeycloakGuardBundle\Guard\KeycloakGuard;
$guard = $this->get('security.guard.keycloak');
$token = $request->headers->get('Authorization');
$user = $guard->authenticate(new KeycloakToken($token));
User Provider Integration
Map Keycloak claims to your User entity via a custom UserProvider:
class KeycloakUserProvider implements UserProviderInterface {
public function loadUserByUsername($username) {
// Fetch user from DB or API, then attach Keycloak claims.
$user->setKeycloakClaims($this->decodeToken($token));
return $user;
}
}
Role-Based Access
Use Keycloak’s realm_access.roles claim for authorization:
security:
access_control:
- { path: ^/admin, roles: [admin] }
make:guard to scaffold a custom guard if extending functionality.api_platform for JWT-protected endpoints:
api_platform:
formats:
jsonld:
mime_types: ['application/ld+json']
patch_formats:
json: true
jsonld: true
security.authentication.success to log or transform user data:
public function onAuthenticationSuccess(AuthenticationSuccessEvent $event) {
$user = $event->getUser();
$user->setLastLogin(new \DateTime());
$this->userManager->update($user);
}
Public Key Rotation
$key = $this->getKeycloakPublicKey(); // Fetch fresh key
$validator = new Lcobucci\JWT\Validation\Validator();
$validator->setLeeway(60); // Allow 1-minute leeway for clock skew.
Token Expiry Handling
Lcobucci\JWT\Token\Plain::fromString($token) to parse expiry claims. Redirect users to Keycloak if expired:
if ($token->isExpired()) {
return $this->redirectToKeycloakLogin();
}
Stateless vs. Stateful
stateless: false and use HttpBasicAuthGuard as a fallback.Claim Mismatches
sub, email) may not match your User entity. Use a UserMapper to resolve conflicts:
class KeycloakUserMapper {
public function mapUserFromKeycloakClaims(array $claims): User {
return User::create([
'username' => $claims['preferred_username'] ?? $claims['email'],
'email' => $claims['email'],
]);
}
}
Enable Debug Mode
Set debug: true in keycloak config to log JWT validation errors:
keycloak:
debug: true
Check var/log/dev.log for detailed validation failures.
Token Inspection Decode JWTs manually to verify claims:
curl -H "Authorization: Bearer $TOKEN" https://jwt.io
Or use PHP:
$decoded = (new Lcobucci\JWT\Parser())->parse($token)->decode();
Custom Guard
Extend KeycloakGuard to add pre-authentication logic:
class CustomKeycloakGuard extends KeycloakGuard {
public function authenticate(TokenInterface $token) {
if (!$this->isValidTokenFormat($token)) {
throw new BadCredentialsException('Invalid token format.');
}
return parent::authenticate($token);
}
}
Register in services.yaml:
services:
App\Security\CustomKeycloakGuard:
tags: [security.guard]
Dynamic Realm Configuration Load realm/config from an API or database:
$config = $this->configService->getKeycloakConfig();
$guard->setConfig($config);
Offline Tokens Support Keycloak’s offline access tokens by extending the validator:
$validator->setIdClaim(new Lcobucci\JWT\Validation\Constraint\IssuedBy($config['auth_server_url']));
$validator->setAudienceClaim(new Lcobucci\JWT\Validation\Constraint\PermittedBy($config['resource']));
How can I help you explore Laravel packages today?