Replace JWT Micropub tokens with revocable opaque tokens

Tokens now store a hashed row in micropub_tokens instead of being
self-contained signed JWTs, so a leaked or unwanted token can actually
be revoked. Since revocation already requires a DB lookup on every
request, JWT's stateless-verification benefit was gone anyway, so this
also drops the lcobucci/jwt dependency entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L
This commit is contained in:
Jonny Barnes 2026-08-13 16:07:07 +01:00
commit d5706b5f8f
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
10 changed files with 124 additions and 181 deletions

View file

@ -5,13 +5,9 @@ declare(strict_types=1);
namespace App\Http\Middleware;
use App\Http\Responses\MicropubResponses;
use App\Models\MicropubToken;
use Closure;
use Illuminate\Http\Request;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Encoding\CannotDecodeContent;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Symfony\Component\HttpFoundation\Response;
class VerifyMicropubToken
@ -39,15 +35,17 @@ class VerifyMicropubToken
], 401);
}
try {
$tokenData = $this->validateToken($rawToken);
} catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) {
$token = MicropubToken::where('token_hash', hash('sha256', $rawToken))
->whereNull('revoked_at')
->first();
if (! $token) {
$micropubResponses = new MicropubResponses;
return $micropubResponses->invalidTokenResponse();
}
if ($tokenData->claims()->has('scope') === false) {
if ($token->scope === '') {
$micropubResponses = new MicropubResponses;
return $micropubResponses->tokenHasNoScopeResponse();
@ -56,26 +54,10 @@ class VerifyMicropubToken
return $next($request->merge([
'access_token' => $rawToken,
'token_data' => [
'me' => $tokenData->claims()->get('me'),
'scope' => $tokenData->claims()->get('scope'),
'client_id' => $tokenData->claims()->get('client_id'),
'me' => $token->me,
'scope' => $token->scope,
'client_id' => $token->client_id,
],
]));
}
/**
* Check the token signature is valid.
*/
private function validateToken(string $bearerToken): Token
{
$config = resolve(Configuration::class);
$token = $config->parser()->parse($bearerToken);
$constraints = $config->validationConstraints();
$config->validator()->assert($token, ...$constraints);
return $token;
}
}