Back to Blog
Lesson 28 of the Advanced Laravel: Architecture, Scaling & Performance course
LaravelJune 28, 20264 min read

JWT and Stateless Security: Architecting Scalable API Authentication

Master stateless API authentication in Laravel. Learn to issue and verify JWTs, implement secure token rotation, and handle revocation in a high-traffic system.

LaravelSecurityJWTAPIAuthenticationArchitecturephpbackend

Previously in this course, we explored Advanced OAuth2 Implementation to handle complex authorization flows. While OAuth2 is the gold standard for delegation, many high-traffic microservices require the speed and simplicity of stateless authentication. In this lesson, we transition to using JSON Web Tokens (JWT) for stateless security, allowing our services to verify identity without constant database lookups.

The Stateless Principle

Stateless authentication relies on the self-contained nature of the JWT. By encoding user identity and permissions directly into the payload, the server doesn't need to query a session store or database to authenticate the user.

However, "stateless" is often misunderstood. While the authentication check is stateless, your system must remain stateful regarding security concerns like token revocation. If a user’s permissions change or their device is stolen, you must be able to invalidate their access.

Issuing and Verifying JWTs

For production-grade Laravel systems, we avoid rolling our own crypto. We rely on established standards, typically using the lcobucci/jwt library or Laravel-native wrappers.

A JWT consists of three parts: Header, Payload, and Signature. The signature is the critical piece that ensures integrity, created by signing the header and payload with a secret key (or private key in RS256).

PHP
#6A9955">// Example: Creating a token with a 1-hour TTL
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;

$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText('your-secret-key'));

$token = $config->builder()
    ->issuedBy('https:#6A9955">//api.your-saas.com')
    ->permittedFor('https:#6A9955">//app.your-saas.com')
    ->identifiedBy('unique-token-id')
    ->issuedAt(now()->toDateTimeImmutable())
    ->expiresAt(now()->addHour()->toDateTimeImmutable())
    ->withClaim('uid', $user->id)
    ->getToken($config->signer(), $config->signingKey());

To verify, we ensure the signature matches and the expiresAt claim is in the future. In a high-traffic environment, you should always use asymmetric signing (RS256) so your microservices can verify tokens using a public key without needing your private signing key.

Handling Token Rotation and Revocation

The biggest pitfall in JWT security is the inability to revoke a token. If you issue a long-lived JWT, you are effectively giving the user an "all-access pass" until it expires.

To solve this, we implement Refresh Token Rotation.

StrategyProsCons
Short-lived JWTsHigh security, no revocation neededFrequent refresh cycles
Revocation Lists (Blacklist)Instant invalidationAdds latency (Redis lookup)
RotationSecure, prevents reuseComplex state management

Implementing Rotation

  1. Access Token: Short lifespan (e.g., 5-15 minutes).
  2. Refresh Token: Long lifespan (e.g., 7 days), stored in a database or Redis.
  3. The Flow: When the access token expires, the client sends the refresh token. You issue a new access token AND a new refresh token, invalidating the old refresh token immediately.

If a refresh token is reused, it's a signal of a potential breach. You should immediately revoke the entire refresh family for that user.

Hands-on Exercise

Your task is to implement a VerifyJwtMiddleware in your modular monolith.

  1. Create a middleware that extracts the Authorization: Bearer token.
  2. Use a public key to verify the signature.
  3. If valid, inject the uid from the token into the Request instance: $request->merge(['user_id' => $payload->uid]);.
  4. Add a check to ensure the jti (JWT ID) is not present in a Redis "blacklist" (used for manual session termination).

Common Pitfalls

  • Storing Secrets in Code: Never hardcode your signing keys. Use environment variables or, preferably, a secret management service like AWS Secrets Manager.
  • Over-stuffing the Payload: Keep the JWT payload small. Large tokens increase header size, which can lead to HTTP 413 "Request Header Fields Too Large" errors in some web servers.
  • Ignoring Clock Skew: Always allow for a small window (e.g., 30-60 seconds) of clock skew when validating the nbf (not before) or exp (expiration) claims to prevent valid tokens from being rejected due to server sync issues.
  • Weak Algorithms: Never use the none algorithm or weak HMAC secrets. Always prefer RS256 or ES256.

Recap

We've moved from session-based security to a stateless model. We've established that while JWTs are stateless, our security posture must account for revocation via token rotation. By using short-lived access tokens and single-use refresh tokens, we maintain high performance without sacrificing the ability to lock down compromised accounts.

Up next, we will address Multi-Tenant Security Isolation, where we ensure that even with valid tokens, users cannot access data belonging to other tenants.

Similar Posts