Installation
composer require abotteram/jwt-bundle
Register the bundle in config/bundles.php (Symfony):
return [
// ...
Abotteram\JwtBundle\AbotteramJwtBundle::class => ['all' => true],
];
Configuration Publish the default config:
php bin/console abotteram:jwt:install
Edit config/packages/abotteram_jwt.yaml:
abotteram_jwt:
secret_key: '%env(JWT_SECRET_KEY)%' # Set in .env
algorithm: 'HS256'
expiration_time: 3600 # 1 hour
First Use Case: Generating a Token
Inject the JwtService and generate a token:
use Abotteram\JwtBundle\Service\JwtService;
class AuthController extends AbstractController
{
public function login(JwtService $jwtService): JsonResponse
{
$user = $this->getUser(); // Assume user is authenticated
$token = $jwtService->generateToken(['id' => $user->id]);
return $this->json(['token' => $token]);
}
}
First Use Case: Validating a Token
Use the JwtListener or manually validate:
use Abotteram\JwtBundle\Service\JwtService;
public function protectedRoute(JwtService $jwtService, Request $request): JsonResponse
{
$token = $request->headers->get('Authorization');
if (!$jwtService->validateToken($token)) {
return $this->json(['error' => 'Unauthorized'], 401);
}
$data = $jwtService->decodeToken($token);
return $this->json(['data' => $data]);
}
generateToken() for custom claims:
$token = $jwtService->generateToken([
'id' => $user->id,
'roles' => $user->roles,
'exp' => time() + 3600, // Override expiration
]);
$accessToken = $jwtService->generateToken(['type' => 'access'], 3600);
$refreshToken = $jwtService->generateToken(['type' => 'refresh'], 86400);
JwtListener to auto-validate incoming requests (configure in config/packages/abotteram_jwt.yaml):
abotteram_jwt:
listener:
enabled: true
protected_paths: ['^/api/protected']
$decoded = $jwtService->decodeToken($token);
if ($jwtService->isTokenExpired($decoded)) {
throw new \RuntimeException('Token expired');
}
use Abotteram\JwtBundle\Service\JwtService;
class JwtAuthenticator extends AbstractGuardAuthenticator
{
public function supports(Request $request): bool
{
return $request->headers->has('Authorization');
}
public function getCredentials(Request $request)
{
return $request->headers->get('Authorization');
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$jwtService = $this->container->get(JwtService::class);
$data = $jwtService->decodeToken($credentials);
return $userProvider->loadUserByIdentifier($data['id']);
}
}
tokens table and validate against it:
// Example: Check if token exists in DB before validating
$token = $request->headers->get('Authorization');
$isValid = $jwtService->validateToken($token) && TokenRepository::exists($token);
.env for sensitive keys:
JWT_SECRET_KEY=your-256-bit-secret
JWT_ALGORITHM=HS256
config/packages/abotteram_jwt_dev.yaml).JwtService to add custom logic:
class CustomJwtService extends JwtService
{
public function generateUserToken(User $user): string
{
return $this->generateToken([
'id' => $user->id,
'username' => $user->username,
'custom_claim' => $this->generateCustomClaim($user),
]);
}
}
RateLimiter to prevent brute-force attacks:
# config/packages/security.yaml
firewalls:
main:
rate_limiter: auth_rate_limiter
JwtService in PHPUnit:
$mockJwt = $this->createMock(JwtService::class);
$mockJwt->method('validateToken')->willReturn(true);
$mockJwt->method('decodeToken')->willReturn(['id' => 1]);
$this->container->set(JwtService::class, $mockJwt);
secret_key in config (even in dev) leaks security.
Fix: Always use %env(JWT_SECRET_KEY)% and ensure .env is in .gitignore.HS256 without a strong secret key or RS256 without a valid keypair.
Fix: For RS256, generate keys with:
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
openssl pkey -in private.pem -pubout -out public.pem
Configure in abotteram_jwt.yaml:
algorithm: 'RS256'
private_key_path: '%kernel.project_dir%/config/jwt/private.pem'
public_key_path: '%kernel.project_dir%/config/jwt/public.pem'
expiration_time or relying solely on exp claim.
Fix: Default to a short TTL (e.g., 1 hour) and use refresh tokens for longer sessions.JwtListener blocking all routes or throwing errors due to missing headers.
Fix: Exclude public paths and validate headers:
abotteram_jwt:
listener:
enabled: true
protected_paths: ['^/api']
header_name: 'X-AUTH-TOKEN' # Custom header
$jwtService->setLeeway(30); // Allow 30-second clock skew
$decoded = $jwtService->decodeToken($token, false); // Skip validation
var_dump($decoded);
abotteram_jwt:
listener:
enabled: false
JwtListener events.config/packages/abotteram_jwt.yaml:
abotteram_jwt:
debug: true
$jwtService->setLogger($this->container->get('logger'));
How can I help you explore Laravel packages today?