Laravel 5.4: Securizando la API
By ercobo | Published | No hay comentarios
Bueno, de las entradas anteriores ya tenemos listo el CRUD de usuarios y las rutas para las llamadas.
Ahora voy a seguir las indicaciones de https://styde.net/api-rest-con-laravel-5-1-proteccion-con-access-key/ para poder securizar la API.
Una vez seguidos los pasos de este tutorial para generar la access key de toda la aplicación y securizarla de manera que nadie que no tenga esa clave la pueda utilizar, es el momento de generar la llamada de login para que un usuario pueda loguearse en la aplicacion con una llamada y recibir un access token que le sirva para poder utilizarla.
Cuándo se creó la tabla de usuarios, se añadió un campo de «api_token» que se autogeneraba cuando se guardaba el usuario por primera vez, así que ahora es el momento de generar la llamada de login. Si recordais del paso anterior, en el archivo «routes/web.php» ya incluimos la ruta de login, para estas llamadas.
También os puede pasar que, despues de seguir los pasos del tutorial de styde.net, os esteis encontrando que las llamadas os devuelven un 401 not authorized si intentais las llamadas sin incluir el access token que ahí está generado.
Pero vamos por pasos. Lo primero es generar el controlador y el modelo que usaremos para el login
1 2 3 4 5 |
PS \api\laravel> php artisan make:controller LoginController Controller created successfully. PS \api\laravel> php artisan make:model Models\Login Model created successfully. PS \api\laravel> |
E incluir la función login() en la clase del controlador, y el modelo, por lo que quedaría así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
namespace App\Http\Controllers; use App\Models\Login; use Illuminate\Http\Request; class LoginController extends Controller { // public function login() { // } } |
Si estamos siguiendo los pasos, ahora mismo al probar la url de login nos pueden pasar dos cosas: o bien nos de el ok sin contenido, o bien nos de un 401 not authorized. Esto ultimo se debe a la comprobación que hace la clase «VerifyAccessKey» que generamos para securizar la API. Lo que tenemos que conseguir es que no haga esta comprobación si las rutas de uso son «/» o «/v1/login».
Para ello, tendremos que modificar lo siguiente (o al menos lo que a mi me ha funcionado):
En App\Http\Kernel.php, ha quedado así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { /** * The application's global HTTP middleware stack. * * These middleware are run during every request to your application. * * @var array */ protected $middleware = [ \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ]; /** * The application's route middleware groups. * * @var array */ protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'throttle:60,1', 'bindings', \App\Http\Middleware\VerifyAccessKey::class, ], ]; /** * The application's route middleware. * * These middleware may be assigned to groups or used individually. * * @var array */ protected $routeMiddleware = [ //'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth' => \App\Http\Middleware\VerifyAccessKey::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, ]; } |
En App\routes\web.php, modifcar el grupo para el prefijo «v1», ha quedado así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Route::group( [ 'prefix' => 'v1' ], function () { Route::post( 'login', 'LoginController@login' ); Route::group(['middleware' => 'auth:api'], function(){ Route::resource( 'users', 'UsersController', ['only' => ['index', 'store', 'update', 'destroy', 'show']] ); }); } ); |
En App\Http\Middleware\VerisyCsrfToken.php, añadir al array $except la ruta de login:
1 2 3 4 |
protected $except = [ // 'v1/login*' ]; |
Ya tenemos la api segura para las llamadas que no sean la raíz y el login. Ahora es el momento de que ese login haga algo. Únicamente va a tener una funcion login para hacer la petición, y devolverá 3 datos para que sean guardados en la sesión del usuario.
LoginController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
/** * PHP Version 7 * Login Controller * * @category Controllers * @package CartaDigitalJCS * @author Javier Cobos Sanz * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License * @link https://cartadigitaljcs.xyz/ */ namespace App\Http\Controllers; //use App\Models\Login; use Illuminate\Http\Request; use App\Models\Login; /** * LoginController Class * * @category Class * @package CartaDigitalJCS * @author Javier Cobos Sanz * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License * @link https://cartadigitaljcs.xyz/ */ class LoginController extends Controller { /** * Login Function * * @param Request $request Request for login * @return array Array of result */ public function login(Request $request) { if ($request->has('user')) { // } else { return response()->json(['error' => 'withoutuser' ], 401); } if ($request->has('password')) { // } else { return response()->json(['error' => 'withoutpassword' ], 401); } $user = $request->input('user'); $password = $request->input('password'); $login = new login(); $login->setuser($user); $login->setpassword($password); if ($login->attempt()) { // Authentication passed... $userdata=$login->getuserdata(); $result=[ 'usuario'=>$userdata->user, 'email'=>$userdata->user_email, 'token'=>$userdata->token, 'result'=>'ok' ]; //return response()->json($result); return response()->json($result); } else { return response()->json(['error' => 'unauthorized' ], 401); } } } |
Models/Login.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
namespace App\Models; use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Hash; class Login extends Model { private $user; private $password; private $userid; private $usertoken; private $userdata; // public function setuser($user) { $this->user=$user; } public function setpassword($password) { $this->password=$password; } public function getuserid() { return $this->userid; } public function getusertoken() { return $this->usertoken; } public function getuserdata() { return $this->userdata; } public function attempt() { $users = DB::table('users')->select( 'usuario as user', 'email as user_email', 'clave as password', 'api_token as token' )->where( [ ['usuario', '=', $this->user], ] )->first(); if (Hash::check($this->password, $users->password)) { $this->userdata=$users; return true; } else { return false; } } } |
Con esto ya tenemos hecho el login y el usuario recibe un token para poder interactuar después con el resto de la API.
En este punto, hemos de cambiar el verificador de token. Al final queda de esta forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
namespace App\Http\Middleware; use Closure; use App\Models\Login; class VerifyAccessKey { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $key = $request->input('usertoken'); if ($key==null) { $key = $request->headers->get('usertoken'); if ($key==null) { return response()->json(['error' => 'notoken' ], 401); } } $login=new login(); $login->setusertoken($key); if ($login->attemptwithtoken()) { return $next($request); } else { return response()->json(['error' => 'tokennovalido' ], 401); } } } |
Y tras esto habra que modificar y probar adecuadamente el controlador de usuarios para ver que todas las llamadas se producen correctamente y ver que si no está el token no funciona y no autoriza a efectuar la operación.
Por hoy es suficiente…