amiltone/keycloack-token-bundle
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require amiltone/keycloack-token-bundle
Ensure your project meets the requirements: PHP 7.2.5+, Symfony 4.4/5.0/6.0.
Keycloak Configuration:
First Use Case:
@UserVerification annotation to a controller method to enforce token validation.
use App\Annotation\UserVerification;
class HomeController extends AbstractController {
/**
* @UserVerification
*/
public function index(Request $request): Response {
$user = $request->attributes->get('user'); // Validated user data
return new Response('Authenticated');
}
}
index:
path: /home
controller: App\Controller\HomeController::index
defaults: { userVerification: true }
Testing:
Authorization header:
Authorization: Bearer <valid_keycloak_token>
@UserVerification on methods to enforce validation. The bundle injects the decoded user data into $request->attributes under the key user.
public function profile(Request $request): Response {
$user = $request->attributes->get('user');
return $this->json($user['preferred_username']);
}
userVerification: true in route defaults for bulk validation.Extract specific claims from the token (e.g., roles, groups):
public function adminDashboard(Request $request): Response {
$user = $request->attributes->get('user');
$roles = $user['realm_access']['roles'] ?? [];
if (!in_array('admin', $roles)) {
throw $this->createAccessDeniedException();
}
return new Response('Welcome, Admin');
}
Combine with Symfony’s security component for role-based access:
# config/packages/security.yaml
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
Validate the token first, then use Symfony’s security system for granular permissions.
Handle short-lived tokens by implementing a refresh endpoint:
public function refreshToken(Request $request): Response {
$refreshToken = $request->request->get('refresh_token');
// Use Keycloak's token endpoint to refresh
$client = new \GuzzleHttp\Client();
$response = $client->post('https://your-keycloak/auth/realms/your-realm/protocol/openid-connect/token', [
'form_params' => [
'client_id' => 'your-client',
'client_secret' => 'your-secret',
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
],
]);
return new JsonResponse(json_decode($response->getBody(), true));
}
Listen to the kernel.request event to parse tokens globally (e.g., for logging or middleware):
// src/EventListener/TokenListener.php
namespace App\EventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class TokenListener {
public function onKernelRequest(RequestEvent $event) {
$request = $event->getRequest();
if ($request->headers->has('Authorization')) {
$token = $this->parseToken($request->headers->get('Authorization'));
$request->attributes->set('user', $token);
}
}
private function parseToken(string $header): array {
// Custom parsing logic or reuse bundle logic
}
}
Register the listener in services.yaml:
services:
App\EventListener\TokenListener:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Use Symfony’s parameter bag to manage Keycloak settings across environments:
# config/packages/keycloak_token.yaml
keycloak_token:
realm: '%env(KEYCLOAK_REALM)%'
client_id: '%env(KEYCLOAK_CLIENT_ID)%'
jwks_uri: '%env(KEYCLOAK_JWKS_URI)%'
Define these in .env:
KEYCLOAK_REALM=your-realm
KEYCLOAK_CLIENT_ID=your-client
KEYCLOAK_JWKS_URI=https://your-keycloak/auth/realms/your-realm/protocol/openid-connect/certs
Authorization header in PHPUnit:
$client->request('GET', '/home', [], [], [
'HTTP_Authorization' => 'Bearer ' . $validToken,
]);
$cache = new \Symfony\Component\Cache\Simple\FilesystemCache();
$jwks = $cache->get('keycloak_jwks', function() {
return file_get_contents($this->jwksUri);
});
AuthenticationUtils to handle 401 responses.401 Unauthorized when tokens expire. Fix: Return a 401 with a WWW-Authenticate header pointing to the refresh endpoint.@UserVerification and defaults: { userVerification: true } are set, the annotation takes precedence. This can lead to unexpected behavior if misconfigured.doctrine/annotations is installed and configured in config/packages/doctrine.yaml.// config/packages/dev/keycloak_token.yaml
keycloak_token:
debug: true
$request->attributes->get('user') returns null. Fix: Check logs for KeycloakTokenException.Add this to config/packages/monolog.yaml:
handlers:
keycloak:
type: stream
path: "%kernel.logs_dir%/keycloak.log"
level: debug
channels: ["keycloak_token"]
Then configure the bundle to use this channel:
keycloak_token:
logging_channel: keycloak
Use the bundle’s KeycloakTokenParser service directly to debug:
$parser = $container
How can I help you explore Laravel packages today?