phpauthenticationjwtmicroservicesdistributed-system

How can I sync JWT blacklists in PHP microservices without a bottleneck or SPOF?


I’m working on a microservices-based project where each service is a separate PHP application. They all rely on JWT for authentication and authorization. The tricky part is revoking (or blacklisting) tokens so that when one service revokes a token, all the others immediately recognize it as invalid.

Right now, each service maintains its own blacklist in a local database. That works well enough until one service invalidates a token while others remain unaware for some time. We’ve considered using a shared cache (like Redis) or a central database for blacklists, but we’d like to avoid creating a single point of failure or a significant bottleneck if that shared component goes down.

Another option we looked at was publishing revocation events through a message broker (like RabbitMQ or Kafka), but that still introduces overhead and depends on every service reliably receiving those events, even if one is temporarily offline. We also tried issuing short-lived tokens so they expire quickly on their own, but that doesn’t really help if you need to revoke one immediately for security reasons.

Below is a simplified code snippet (using the firebase/php-jwt library) showing how a service checks if a token is blacklisted:

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

function isBlacklisted(string $jti): bool
{
    // This function queries a local database
    // that stores blacklisted jti values
    return false; // Placeholder
}

$jwt = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$secretKey = 'MY_SECRET_KEY';

try {
    // Decode and verify the signature
    $decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));

    // If the token has a jti (JWT ID), check if it's blacklisted
    $jti = $decoded->jti ?? null;
    if ($jti && isBlacklisted($jti)) {
        throw new Exception('Token has been revoked');
    }

    // Token is valid and not blacklisted; proceed
    echo "Token is valid!";
} catch (Exception $e) {
    // Handle invalid or revoked token
    echo "Invalid or revoked token!";
}

The question is: How can we keep all services in sync about revoked tokens without depending on a centralized data store or service? If anyone has faced a similar scenario or found a creative workaround, I’d really appreciate any insights or suggestions.

Thanks!


Solution

  • Keeping TTLs short is the key bit, and you should keep that in place regardless.

    The additional overhead/complexity involved for immediate revocation is the price of the requirement. Revocation is not something that can be divined from a token signature, it is inherently "centralized" information that must be retrieved or delivered, one way or another.

    If that juice is worth the squeeze, then the decision that I see here where to add that additional complexity:

    While the latter seems preferable on the surface, it's up to you to determine which best fits your current app stack. Eg:

    Lastly, neither has to be a single point of failure, as either can be deployed in a redundant, clustered manner. You can also implement the check itself as fail-open and rely on the aforementioned short token TTLs as the fail-safe.