Skip to content

Conversation

@Tresor-Kasenda
Copy link

This PR adds a route decorator attribute that automatically validates signed URLs, eliminating the need for manual validation in each controller method.

Motivation

Currently, when using signed URLs, developers must manually validate the signature in every controller method:

final class PasswordlessAuthenticationController
{
    public function __construct(
        private readonly UriGenerator $uri,
    ) {}

    public function __invoke(Request $request): Response
    {
        if (! $this->uri->hasValidSignature($request)) {
            return new Forbidden();
        }

        // Actual logic here...
    }
}

This is repetitive, error-prone, and clutters controller code with validation logic.

Solution

The new #[ValidSignature] attribute handles validation automatically:

#[Get('/verify-email/{token}')]
#[ValidSignature]
public function verifyEmail(string $token): Response
{
    // This code only executes if the signature is valid
    // 403 Forbidden is returned automatically if invalid
}

Features

  • Declarative validation - Just add the attribute, no boilerplate code
  • Automatic 403 response - Returns Forbidden when:
    • Signature is missing
    • Signature is invalid (URL was tampered)
    • Signature has expired (for temporary signed URLs)
  • Works with existing signed URL functions - Compatible with signed_uri() and temporary_signed_uri()
  • Route-specific middleware - Only runs on routes with the attribute (not globally)

Usage Examples

Permanent signed URL

// Generate URL
$url = signed_uri(
    action: [EmailVerificationController::class, 'verify'],
    userId: $user->id,
    email: $user->email,
);

// Controller - automatically validates signature
#[Get('/verify-email')]
#[ValidSignature]
public function verify(int $userId, string $email): Response
{
    // Safe to proceed - signature was validated
}

Temporary signed URL

// Generate URL with 10-minute expiration
$url = temporary_signed_uri(
    action: PasswordResetController::class,
    duration: Duration::minutes(10),
    token: $resetToken,
);

// Controller - automatically validates signature AND expiration
#[Get('/reset-password/{token}')]
#[ValidSignature]
public function __invoke(string $token): Response
{
    // Safe to proceed - signature is valid and not expired
}

brendt and others added 4 commits December 12, 2025 09:08
…L validation

Add a route decorator attribute that automatically validates signed URLs,
eliminating the need for manual validation in each controller method.

Features:
- New #[ValidSignature] attribute to enforce signature validation on routes
- Returns 403 Forbidden when signature is missing, invalid, or expired
- Works with both permanent and temporary signed URLs

Usage:
  #[Get('/verify-email/{token}')]
  #[ValidSignature]
  public function verifyEmail(string $token): Response
  {
      // This code only executes if the signature is valid
  }

Files added:
- packages/router/src/ValidSignature.php - Route decorator attribute
- packages/router/src/ValidSignatureMiddleware.php - Validation middleware
- tests/Integration/Route/ValidSignatureMiddlewareTest.php - 8 integration tests
- tests/Integration/Route/Fixtures/SignedUrlController.php - Test fixture
Copilot AI review requested due to automatic review settings February 6, 2026 12:09

This comment was marked as outdated.

Copy link
Member

@innocenzi innocenzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call the middleware ValidateSignatureMiddleware.

I think we also need a convention for the route attributes, cc @brendt.

@innocenzi innocenzi changed the title Add #[ValidSignature] attribute for automatic signed URL validation feat(router): add signature validation middleware and route attribute Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants