abel/keycloak-bearer-only-adapter-bundle
Symfony bundle to secure APIs with Keycloak Bearer-Only clients. Provides adapter and configuration (issuer, realm, client id/secret) via Symfony Flex recipe or manual YAML/.env setup. Supports Keycloak distribution differences (e.g., /auth removal).
Install the Bundle
composer require abel/keycloak-bearer-only-adapter-bundle
composer config extra.symfony.allow-contrib true
Run composer update to apply the Symfony Flex recipe.
Configure Keycloak
.env:
OAUTH_KEYCLOAK_ISSUER=http://localhost:8080/auth/realms/{realm}
OAUTH_KEYCLOAK_REALM={realm}
OAUTH_KEYCLOAK_CLIENT_ID={client_id}
OAUTH_KEYCLOAK_CLIENT_SECRET={client_secret}
Enable the Bundle
Add to config/bundles.php (if not auto-loaded):
return [
// ...
Abel\KeycloakBearerOnlyAdapterBundle\AbelKeycloakBearerOnlyAdapterBundle::class => ['all' => true],
];
First Use Case: Protect a Controller
Annotate a controller method with [BearerOnly]:
use Abel\KeycloakBearerOnlyAdapterBundle\Annotation\BearerOnly;
class ApiController extends AbstractController
{
#[BearerOnly]
public function secureEndpoint(): JsonResponse
{
return new JsonResponse(['message' => 'Access granted!']);
}
}
Authentication Middleware
The bundle integrates with Symfony’s security system. Use it alongside api_platform or mercure for granular control:
# config/packages/security.yaml
security:
firewalls:
api:
pattern: ^/api
stateless: true
bearer_only: true # Enables the bundle's logic
Token Validation
The bundle validates JWT tokens against Keycloak’s public key. Extend validation logic by implementing TokenValidatorInterface:
use Abel\KeycloakBearerOnlyAdapterBundle\Security\TokenValidatorInterface;
class CustomTokenValidator implements TokenValidatorInterface
{
public function validate(string $token): bool
{
// Custom logic (e.g., check token claims)
return true;
}
}
Register the service in services.yaml:
services:
Abel\KeycloakBearerOnlyAdapterBundle\Security\TokenValidator:
arguments:
$validator: '@custom_token_validator'
Role-Based Access Control (RBAC) Fetch roles from the token and apply Symfony’s voter system:
#[BearerOnly]
public function adminEndpoint(): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return new JsonResponse(['message' => 'Admin access granted!']);
}
Integration with API Platform
Use the bundle’s BearerOnly attribute alongside ApiPlatform\Metadata\ApiResource:
#[ApiResource]
#[BearerOnly]
class User {}
Bearer-Only Client Requirement
confidential) will fail silently or throw errors.Token Expiration Handling
TokenRefresher or use a library like league/oauth2-client for refresh logic.401 Unauthorized responses to monitor token expirations.CORS and CSRF
Authorization: Bearer {token}
security.yaml:
security:
firewalls:
api:
csrf_protection: false
Environment Variables
OAUTH_KEYCLOAK_*). Typos (e.g., KEYCLOAK_ISSUER) will cause silent failures.php artisan config:dump to verify loaded values.Enable Debug Mode
Set APP_DEBUG=true in .env to see detailed validation errors (e.g., malformed tokens, issuer mismatches).
Log Token Claims Dump the decoded token for inspection:
use Firebase\JWT\JWT;
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$decoded = JWT::decode($token, null, ['RS256']);
var_dump($decoded);
Keycloak Public Key Rotation
/auth/realms/{realm}/protocol/openid-connect/certs endpoint for changes.use Symfony\Contracts\Cache\CacheInterface;
class KeycloakKeyCache
{
public function __construct(private CacheInterface $cache) {}
public function getPublicKey(): string
{
return $this->cache->get('keycloak_public_key', function () {
return file_get_contents('https://.../certs');
});
}
}
Custom Token Extraction
Override the default Authorization: Bearer header logic by implementing TokenExtractorInterface:
use Abel\KeycloakBearerOnlyAdapterBundle\Security\TokenExtractorInterface;
class CustomTokenExtractor implements TokenExtractorInterface
{
public function extractToken(): ?string
{
return $_GET['token'] ?? null; // Example: Query param fallback
}
}
Event Listeners
Listen to security.interactive_login or kernel.exception to log token validation events:
services:
App\EventListener\TokenValidationListener:
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login', method: 'onTokenValidation' }
Testing
Use Symfony\Panther or Guzzle to simulate authenticated requests in tests:
use Symfony\Component\Panther\PantherTestCase;
class ApiTest extends PantherTestCase
{
public function testSecureEndpoint()
{
$client = static::createPantherClient();
$client->request('GET', '/api/secure', [
'headers' => ['Authorization' => 'Bearer ' . $this->getValidToken()]
]);
$this->assertResponseIsSuccessful();
}
}
How can I help you explore Laravel packages today?