Aplicar seguridad a nuestra API con Lumen

La seguridad es un tema primordial que debemos tener presente en todos nuestros desarrollos. En esta guía abordamos como aplicar seguridad a nuestra API.

Partimos creando nuestro proyecto

composer create-project --prefer-dist laravel/lumen lumen-security

Una vez creado, vamos al archivo bootstrap/app.php y debemos descomentar lo siguiente

$app->routeMiddleware([
    'auth' => App\Http\Middleware\Authenticate::class,
]);

$app->register(App\Providers\AuthServiceProvider::class);

No obstante, para este ejemplo también debemos descomentar

$app->withFacades();
$app->withEloquent();

Vamos al controlador app/Http/ExampleController.php y agregamos el método index

public function index()
{
    return 'hola';
}

Ahora vamos a las rutas y creamos la ruta a este método, indicando que necesitamos estar autenticados

$app->get('/', ['middleware' => 'auth',
    'uses' => 'ExampleController@index'
]);

Si ahora intentamos abrir la ruta, nos retorna Unauthorized. Para entender el porqué recibimos esto, debemos ir al archivo app/Providers/AuthServiceProvider.php y buscamos el método boot.

public function boot()
{
    // Here you may define how you wish users to be authenticated for your Lumen
    // application. The callback which receives the incoming request instance
    // should return either a User instance or null. You're free to obtain
    // the User instance via an API token or any other method necessary.

    $this->app['auth']->viaRequest('api', function ($request) {
        if ($request->input('api_token')) {
            return User::where('api_token', $request->input('api_token'))->first();
        }
    });
}

Aquí nos damos cuenta que, para validar el usuario, está buscando en la base de datos el api_token con el que está comparando lo que se está enviando como parámetro. Entonces, vamos y creamos una migración

php artisan make:migration create_users_table

Y luego creamos una migración simple (a modo de ejemplo)

increments('id');
            $table->string('email', 60);
            $table->string('password', 100);
            $table->string('api_token', 100);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('users');
    }
}

Finalmente corremos la migración

php artisan migrate

Ahora, para las pruebas, necesitamos usuarios, por lo que creamos un seeder

php artisan make:seeder UsersTableSeeder

y dejamos el archivo de la siguiente manera

insert([
            'email' => str_random(10).'@gmail.com',
            'password' => app('hash')->make('secret'),
            'api_token' => str_random(40)
        ]);
    }
}

Agregamos el seeder a database/DatabaseSeeder.php

call('UsersTableSeeder');
    }
}

Y corremos la migración

php artisan db:seed

Si miramos la db y agregamos el api_key en la ruta, nos daremos cuenta que ya no nos da el error, por ejemplo:

lumen-security.dev?api_token=atE3KEA6TbwLgfyhNhSrgOPs73G2EOEPkZxX4hYk

Como podemos darnos cuenta, aquí ya estamos aplicando un poco de seguridad, ya que en cada solicitud tenemos que enviar el token, no obstante siempre tendrá el mismo token?, actualmente la respuesta es si, pero no es lo más conveniente. Aquí tenemos más de una forma, una de ellas es, por ejemplo, en cada login, hacer un update en el campo api_token.

Otro dato a tener en cuenta, es que si queremos cambiar la forma predeterminada en que trabaja la autenticación, vamos a app/Http/Middleware/Authenticate.php y buscamos el método handle. Si analizamos el método, encontramos que cada vez que el usuario no ha hecho login, retorna un 401.

public function handle($request, Closure $next, $guard = null)
{
    if ($this->auth->guard($guard)->guest()) {
        return response('Unauthorized.', 401);
    }

    return $next($request);
}

Esto lo podemos cambiar según nuestra necesidad.


Tu opinión es importante para mi, ¿Te ha resultado útil este artículo?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*