Banner Signer URL Laravel

Es posible que en un proyecto tengamos la necesidad de asegurarnos que una URL sea única y no haya sido modificada por el usuario, pues en Laravel tenemos la opción de generar URLs con un hash único con el cual podemos validar que sea la URL correcta. Así como también podemos añadirle a las URLs un tiempo de expiración ideal para enlaces a ofertas, transmisiones multimedia, eventos, etc. Este tipo de URL son conocidas como Signed URLs o Direcciones Firmadas y en este tutorial vamos a conocerlas.

Este es un feature incorporado en Laravel desde Marzo del 2018 en la versión 5.6.12.

La idea es crear una URL como normalmente lo hacemos pero adicionalmente haremos que Laravel firme dicha URL, esto es, agregándole un hash único a la cadena query de la URL.

Por tanto, si queremos comprobar que la Signed URL a la que ha accedido un usuario es válida, Laravel hace la comprobación mediante la porción de la cadena query del URL. Si el usuario cambia algo en la cadena entonces el hash no coincidiría con el original y, por lo tanto, vamos a poder restringir su acceso.

Hay dos tipos de Signed URL las temporales y las permanentes. Es decir que podemos generar opcionalmente una URL con un tiempo de expiración para una ruta con nombre en tu aplicación Laravel como lo veremos a continuación:

Signed URL Temporales

Son URLs que podemos crear con un hash único y además tendrán un tiempo de vida limitado.

Observemos este pequeño ejemplo. Supongamos que en nuestra aplicación ofrecemos a los usuarios registrarse a un evento solo por un cierto período de tiempo:

Primero, creamos un par de rutas en nuestro archivo routes/web.php para permitir a los usuarios solicitar la suscripción a un evento y la otra para obtener el enlace temporal del usuario:

Route::get('event/{event}/{user}', 'EventController@subscribe')
     ->name('event.subscribe');

Route::get('event/link-subscribe', 'EventController@getLinkSubscribe')
     ->name('event.getLinkSubscribe');

La ruta que vamos a firmar tiene por nombre event.suscribe y esto es importante ya que hemos mencionado que para crear una URL única para una ruta es necesario que tenga un nombre.

Seguidamente se hace referencia a un controlador el cual podemos crear con el siguiente comando:

php artisan make:controller EventController

En este controlador vamos a colocar un par de métodos llamados getLinkSubscribesubscribe los cuales van a contener nuestra lógica:

<?php

namespace App\Http\Controllers;

use App\{Event, User};
use Illuminate\Http\Request;
use \Illuminate\Support\Facades\URL;

class EventController extends Controller
{
    public function getLinkSubscribe()
    {
        return URL::temporarySignedRoute(
            'event.subscribe', 
            now()->addMinutes(5), 
            ['event' => Event::first(), 'user' => auth()->user()]
        );
    }

    public function subscribe(Request $request, Event $event, User $user)
    {
        if (! $request->hasValidSignature()) {
            abort(403);
        }
     
        $event->users()->attach($user);

        return response('Te has suscrito al evento.');
    }   
}

En este controlador definimos las acciones. En el método getLinkSubscribe hemos utilizado el método temporarySignedRoute del Facade URL, el cual creará una URL temporal firmada para cuando un usuario de la aplicación acceda a ella reciba el enlace de suscripción firmado para un evento.

Los parámetros que recibe el método temporarySignedRoute son tres:

  • El nombre de la ruta,
  • El momento en que expira.
  • Un arreglo con los parámetros de la ruta que se está firmando.

Un ejemplo de una URL temporal generada por el método puede ser algo como esto:

http://example.test/event/1/1/?expires=1552933042&signature=a8b7a16ec7b49f8e367c12e2be5bff0a8ea0dfc872100cc0d2e390a5f713243b

Luego cuando el usuario hace clic en el enlace se van a ejecutar las acciones del método subscribe del controlador:

Dentro de subscribe vamos a encontrar un método que puede ser nuevo para nosotros, se trata del método hasValidSignature el cual se encargará de verificar que la URL con la cual se está accediendo a la ruta sea correcta y no haya sido modificada o esté expirada. En caso contrario, devolverá una excepción o lo que definamos.

De esta manera, si usuario intenta modificar la URL que recibe como por ejemplo: incrementar el tiempo de expiración, modificar el ID del evento o del usuario; esto no será posible, ya que la ruta está firmada con el hash único y por tanto recibiría un error 403 gracias a que el método hasValidSignature comprueba que la ruta sea correcta según como lo hemos definido en nuestro ejemplo.

Signed URLs permanentes

En nuestro ejemplo anterior hemos creado una ruta firmada con el método temporarySignedRoute la cual tiene un tiempo de expiración. En caso de necesitar una URL permanente podemos utilizar el método signedRoute.

Podemos asociar lo que nos ofrece una Signed Route permanente con una ruta para cancelar la suscripción de un newsletter o retirarse del registro a un evento.

Para continuar con nuestro ejemplo podemos crear dos nuevas rutas:

Route::get('unsubscribe-event/{event}/{user}', 'EventController@unsubscribe')
   ->name('event.unsubscribe');

Route::get('event/link-unsubscribe', 'EventController@getLinkUnsubscribe')
   ->name('event.getLinkUnsubscribe');

Además, añadir los dos métodos adicionales al controlador:

<?php

namespace App\Http\Controllers;

use App\{Event, User};
use Illuminate\Http\Request;
use \Illuminate\Support\Facades\URL;

class EventController extends Controller
{
    //... métodos para suscribirse a un evento

    public function getLinkUnsubscribe()
    {
        return URL::signedRoute(
            'event.unsubscribe',
            ['event' => Event::first(), 'user' => auth()->user()]
        );
    }

    public function unsubscribe(Request $request, Event $event, User $user)
    {
        if (! $request->hasValidSignature()) {
            abort(403);
        }
     
        $event->users()->detach($user);

        return response('Has cancelado tu suscripción al evento.');
    }   
}

Este método signedRoute recibe como primer parámetro el nombre de la ruta y como segundo un arreglo con los parámetros de la ruta que estamos firmando. Su funcionamiento es el mismo que el de las rutas temporales a diferencia de que éstas no tienen tiempo de expiración.

Middleware Signed

En el ejemplo hemos verificado que las rutas sean correctas utilizando el siguiente código:

if (! $request->hasValidSignature()) {
    abort(403);
}

Quizás esto puede ser repetitivo ya que si tenemos muchas rutas firmadas vamos a tener que utilizar el condicional siempre. Una alternativa a esto es utilizar un Middleware que ya viene registrado en nuestra aplicación de Laravel. De hecho lo podemos encontrar en el archivo app/Http/Kernel.php dentro del arreglo $routeMiddleware:

'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,

El Middleware se encargará de comprobar que la ruta sea correcta y, en caso de que no lo sea, disparará una excepción InvalidSignatureException. En caso de querer cambiar el comportamiento del Middleware puedes extenderlo en uno nuevo y editar el arreglo en el archivo Kernel.php.

Para implementarlo a una ruta entonces simplemente podemos usar el método middleware o encerrarlo en un grupo de rutas con el middleware aplicado como se muestra a continuación:

// Uso del método middleware en una ruta en particular
Route::get('event/{event}/{user}', 'EventController@subscribe')
    ->name('event.subscribe')
    ->middleware('signed');

// Grupo de rutas
Route::group(['middleware' => 'signed'], function () {
    Route::get('event/{event}/{user}', 'EventController@subscribe')
        ->name('event.subscribe');
    Route::get('unsubscribe-event/{event}/{user}', 'EventController@unsubscribe')
        ->name('event.unsubscribe');
});

Si deseas conocer un poco más sobre los middleware en Laravel te invito a mirar el tutorial de Cómo crear y usar los Middleware en Laravel.

Terminamos

Las signed URLs o direcciones firmadas son un feature que quizás no vamos a requerir todos los días pero es algo que tenemos a la mano y que podemos usar en cualquier momento de forma muy simple gracias a Laravel. Si te ha gustado este material por favor compártelo en las redes sociales.

Suscríbete a nuestro boletín

Te enviaremos publicaciones con consejos útiles y múltiples recursos para que sigas aprendiendo.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.