How to Assert Redirects to Signed URLs in Laravel Tests
Tags: laravel, php, testing, webdev
Published: 2021-08-17 20:29:14

## Introduction

I recently made a <a href="https://github.com/laravel/framework/pull/38349" target="_blank" rel="noopener noreferrer">pull request</a> that got merged into the Laravel framework and was released in <a href="https://github.com/laravel/framework/releases/tag/v8.55.0" target="_blank" rel="noopener noreferrer">v8.55</a>. It added a new `assertRedirectToSignedRoute()` method that you can use in your tests.

I've been using this method in a few of my projects for quite a while by adding it using macros, but I decided to make a pull request to the framework to try and help other developers who could probably make use of it too.

By the way, if you're interested in learning about macros, check out my [How to Swap Items in an Array Using Laravel Macros](https://ashallendesign.co.uk/blog/how-to-swap-items-in-an-array-using-laravel-macros) article.

## What Are Signed Routes?

Before we look at testing the signed URLs, let's take a quick look at what they are and what they can be used for.

Signed URLs are just URLs to named routes in your application that contain a signature in the query string.

Here's an example of a signed URL:

***https://my-website.co.uk/protected?signature=41cacd19b33b5d29d46876762e6b0091e236a8c2ab59a656df88a31b500471e1***

As you can see, the URL contains a `signature` parameter.

Now, if we were to try and go to this route, we could use the `signed` middleware to verify that the URL hadn't been tampered with since it was created. This can be really useful when we want to create a route that should be publicly accessible but that needs a bit of protection. If the URL has changed in any way, when someone navigates to it, they'll receive a 403 error response.

You can generate the signed URLs using code such as this:

```php
use Illuminate\Support\Facades\URL;

return URL::signedRoute('protected-route', ['user' => 1]);
```

If you've subscribed to my newsletter and have eagle eyes, you might have actually noticed that I use signed routes in the 'Confirm Subscription' email. The signed route looks like this:

***https://ashallendesign.co.uk/subscribe/confirm?email=example%40example.com&signature=f6def8b181b2ff43629994bda724f6ada19b3677bbaf09bc656ecde7ba1144c3***

When someone goes to this route, it confirms that they want to subscribe to the newsletter. By signing this URL, I can have confidence that it hasn't been tampered with and that someone isn't trying to flood my newsletter and sign up lots of random email addresses.

## What Are Temporary Signed URLs?

The signed URLs discussed above don't have an expiry date and generally should be accessible indefinitely. However, there might be times that you want to make a signed URL that can be accessed for a specific amount of time. These work in the same way as the generic signed URLs but they also have an expiry time in the query string.

Here's an example of a temporary signed URL:

***https://my-website.co.uk/protected?expires=1628978674&signature=ec582ea358f0d357edd91fd456a01b0e0b0a3bc62fb460a2853a82c921cd6f54***

As you can see, the URL contains `signature` and `expires` parameters.

In a similar way to the generic signed URLs, if the URL is tampered with or if someone tries to navigate to it after the expiration time, a 403 error response will be returned.

## Testing Redirects to Signed Routes

Now that we've briefly covered signed routes in Laravel, we can look at how to write a test to assert that a controller redirects to a signed URL. Please note that the tests below are going to be very basic and we're only going to be testing the redirect; but, hopefully, they should help to explain the overall concept.

Let's imagine that we have the following single-use controller that has the route `/my-route`:

```php
class RandomController extends Controller
{
    public function __invoke(): RedirectResponse
    {
        // Do something...
        
        return redirect()->signedRoute('example.route', ['param' => 'hello']);
    }
}
```

If we wanted to test that the controller returned a redirect to a signed route but didn't particularly care what the URL was, we could write a test like so:

```php
/** @test */
public function user_is_redirected_to_signed_route(): void
{
    $this->get('/my-route')
        ->assertRedirectToSignedRoute();
}
```

Alternatively, if we wanted to be stricter and check that the redirect was to a specific route, we could write the following test:

```php
/** @test */
public function user_is_redirected_to_signed_route(): void
{
    $this->get('/my-route')
        ->assertRedirectToSignedRoute(route('example.route', ['param' => 'hello']));
}
```

The `assertRedirectToSignedRoute()` method also works with temporary signed routes. So, if our controller returned `redirect()->temporarySignedRoute()` rather than `redirect()->signedRoute()`, the tests above would still work.

## Conclusion

Although this post is a bit shorter than usual, hopefully it should have given you a brief overview of signed URLs in Laravel and how to write tests that your controllers redirect to them correctly.

If this post helped you out, I'd love to hear about it. Likewise, if you have any feedback to improve this post, I'd also love to hear that too.

If you're interested in getting updated each time I publish a new post, feel free to [sign up for my newsletter](https://ashallendesign.co.ul/blog).

Keep on building awesome stuff! 🚀