Crear Cliente tipo Password para API RESTful con laravel

Para conectarse a una API puedes utilizar Laravel y sus componentes para conseguir un sitema seguro y fiable.

En este ejemplo vamos a ver como implentar un cliente API para conectarse y autenticarse en una API creada por nosotros mimsos, que puedes ver en el post de cómo crear una API RESTful con laravel.

Índice
  1. Crear nuevo proyecto cliente
  2. Configurar un nuevo servicio en configuración
  3. Crear base de datos para el cliente API
  4. Instalar sistema de autenticación con Laravel Breeze
  5. Modificaciones de login en la API
  6. Crear Modelo y Migración Access Token
  7. Modificar el controlador de autentificación por defecto
  8. Modificar el controlador de registro defecto
  9. Crear credenciales de tipo password

Crear nuevo proyecto cliente

Se puede craear el proyecto laravel a través de la línea de comandos y utilizando el comando

laravel new nombreproyecto

Configurar un nuevo servicio en configuración

Hay que indicar que se va a utilizar un nuevo servicio para llamar a una API. Entoces en el fichero services se añade una propiedad con los valores necesarios, por ejemplo:

config\services.php

'miapi' => [
        'url_api' => env('APP_URL_API'),
        'client_id' => env('API_CLIENT_ID'),
        'client_secret' => env('API_CLIENT_SECRET'),
    ],

El detalle de los parámetros se definen en el fichero .env, para evitar publicar estos datos confidenciales. Y buenas prácticas copiar también en el fichero .env.example, para dar a conocer las variables a quien utilice la aplicación web.

.env
.env.example

APP_URL_API=http://api.test/
API_CLIENT_ID=95b4f637-6d8b-4272-9bc0--------
API_CLIENT_SECRET=cmig33lyYrqKnI5i7Ai--------

El client_id y el client_secret se han creado desde el servidor API al no nos vamos a conectar con este cliente que estamos creando. Para ello en la API servidor ejecutar el comando de creación de credenciales tipo password

php artisan passport:client --password

Una vez definidas las propiedades, conviente cachearlas en Laravel con el comando config:cache

php artisan config:cache

Ahora no es necesario pero apuntar que para limpiar la caché se hace con la opción :clear

php artisan config:clear

Crear base de datos para el cliente API

Partiendo de que ya se sabe como crear una base de datos, en este ejemplo se usará phpmyadmin en un servidor local bajo Xamp.

Acceder a la url del servidor y poner phpMyAdmin. Ir al panel lateral izquierdo y hacer clic en Nueva. Poner el nombre deseado y hacer clic en botón crear.

php my admin crear nueva base de datos

Después ir al fichero de configuración del proyecto en Laravel y poner la dirección y nombre de la base de datos.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=mibasededatos
DB_USERNAME=root
DB_PASSWORD=

Instalar sistema de autenticación con Laravel Breeze

Una vez creado y situado dentro se instala el paquete de autenticación Laravel Breeze para tener un fácil y rápido sistema de control de usuarios. Mira como instalar Laravel Breeze.

En resumen son los siguientes comandos:

composer require laravel/breeze --dev
php artisan breeze:install
npm install
npm run dev
php artisan migrate

Modificaciones de login en la API

Para hacer login en la API y no en la propia base de datos, hay que hacer unas modificaciones.

Modificar migración User

Abrir el fichero de migraciones de usuarios create_users_table.php. En el ejemplo xxx es la fecha del fichero.

database\migrations\xxx_create_users_table.php

Quitar la creación de verificcación de email y password

$table->timestamp('email_verified_at')->nullable();
$table->string('password');

Quedaría así

   public function up()
    {
        //quitar verificacion de email y contraseña
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->rememberToken();
            $table->timestamps();
        });
    }

Modificar modelo User

Abrir el fichero de Modelo para quitar el guardado masivo de algunos campos, que figura en la propiedad fillable y hidden

app\Models\User.php

Quitar password del array

'password,

quedaría así:

protected $fillable = [
 'name',
 'email',
];

protected $hidden = [
 'remember_token',
];

Ademas agregar la relación con la tabla que se va a vincular. Será una relación uno a un 1:1 entre el id del usuario de la base de datos del cliente y la base de datos de la API.

public function accessToken(){
 return $this->hasOne(AccessToken::class);
}

Ahora ya toca crear la migración y modelo para guardar y comprobar el access token o acceso.

Crear Modelo y Migración Access Token

Para crear los dos ficheros en un mismo comando se puede ejecutar el siguiente código, el cual genera dos ficheros:

php artisan make:model AccessToken -m

  • database\migrations\xxx_create_access_tokens_table.php
  • app\Models\AccessToken.php

Modificar migración Access Token

La migración creará una tabla para guardar los datos de usuario referenciados a la API

La relacion será de uno a uno 1:1 con tabla users del cliente.
Utilizar constrainded para crear relación automática por convencion.
Guardar en service_id el id del usuario en la bd de la API.
Guardar en user_id el id del usuario de la parte del cliente.

public function up(){
 Schema::create('access_tokens', function (Blueprint $table) {
  $table->id();

  $table->foreignId('user_id')->constrained();
  $table->unsignedBigInteger('service_id');
  $table->text('access_token');
  $table->text('refresh_token');
  $table->dateTime('expires_at');

  $table->timestamps();
 });
}

Modificar modelo Access Token

Lo primero crear la asignación masiva con la propiedad fillable, para indicar los datos necesarios a guardar.

 protected $fillable = [
        'user_id',
        'service_id',
        'access_token',
        'refresh_token',
        'expires_at',
];

Limpiar tokens caducados

Para evitar llenar la base de datos con tokens caducados y que no se volverán a utlizar, es conveniente limpiarlos.

Laravel Passport permite limpiar esta basura de dos formas

  1. Mediante la consola o línea de comandos tecleando passport y l
    artisan passport:purge
  2. La otra opción es desde programación, dentro de la aplicación
    1. Abrir fichero kernel
      app\Console\Kernel.php
    2. Editar método schedule indicando el comando y la periodicidad (hourly, daily, everyMinute, etc.)
      protected function schedule(Schedule $schedule){
      $schedule->command('passport:purge')->everyMinute();
      }

Estas tareas se ejecutan por defecto en el cron del servidor, pero en entorno de desarrollo se puede simular con el comando schedule:work

php artisan schedule:work

Hasta parar el servicio con Ctrl+z seguirá activo.

Modificar el controlador de autentificación por defecto

Para que el cliente se autentique en la api, hay que modificar el controlador AuthenticatedSessionController.

app\Http\Controllers\Auth\AuthenticatedSessionController.php

Importar el Facade Http

use Illuminate\Support\Facades\Http;

Resumen de modificación del metodo store

Resumidamente se hará lo siguiente:

  1. Modificar párametro de entrada del método store
  2. Verificar usuario con email y contraseña.
  3. Petición a la API.
  4. Verificar que la respuesta no sea 404, y si lo fuera devolver a la vista de login con mensaje de error.
  5. Almacena respuesta tipo json en una variable.
  6. Insertar o actualizar registro en a base de datos del cliente.
  7. Realizar nueva petición para conseguir un access_token, y guarda información.
  8. Comprobar si el usuario ya dispone de un access_token, y si no tiene, insertar un registro que relacione usuario y el token en la base de datos del cliente
  9. Loguear al usuario, con el check opcional de recordar.
  10. Redirigir a una ruta deseada. Por defecto irá al Dashboard.

Modificación método store

Comenzamos con las modificaciones, cambiando el parámetro LoginRequest por Request

Además validar el email y password, y guardar el resultado de la llamada con el Facade Http a la API y conocer si devuele error 404 volver a la página anterior con método back.

public function store(Request $request)
{
 $request->validate([
  'email' => 'required|string|email',
  'password' => 'required|string'
 ]);


 $response = Http::withHeaders([
   'Accept' => 'Application/json'
 ])->post('http://api.test/login', [
  'email' => $request->email,
  'password' => $request->password
 ]);


 if($response->status() == 404){
  return back()->withErrors('These credentials do no match our records.');
 }

Es buen momento para abrir el navegador y probar que funciona el login del cliente con la base de datos del la API. Recomendable limpiar caché con el comando config:cache

php artisan config:cache

Provisionalmente puedes ver en pantalla del navegador web lo que devuelve en ese momento colocando un return del formato json

$response = $response->json();
return $response['data'];

Para grabar el registro utilizar updateOrCreate, el cual comprueba que sea si no existe lo cree, pero si ya existe lo actualice.

...

$response = $response->json();

$user = User::updateOrCreate([
 'email' => $request->email
 ], $response['data']);

...


En este punto, si se ha consiguido autenticar como usuario, ya se puede recibir el access token que permitirá comunicarse con la API.

Comprobar que el usuario tiene ya un token, y si no dispone de uno, insertarlo en la base de datos.

El client_id y el client_secret ven definidos en el fichero .env, y recuperados a través del método config() tal cual puedes ver en Agregar Parámetros por configuración. De esta forma guardamos la privacidad de los datos al subir nuestro proyecto a repositorios o bien compartir el proyecto de cualquier otra forma.

...

if(! $user->accessToken){

$response = Http::withHeaders([
 'Accept' => 'Application/json'
 ])->post('http://api.test/oauth/token', [
  'grant_type' => 'password',
  'client_id' => config('services.api.client_id'),
  'client_secret' => config('services.api.client_secret'),
  'username' => $request->email,
  'password' => $request->password
 ]);

Para ver si todo va correcto, se puede utilizar le método dd para mostar los datos en el navegador.


dd($response->json());

Guardar los datos del response y calcular la fecha de expiración, la cual será la fecha actual y se le suma el tiempo del campo expires_in.

...

 $access_token = $response->json();

 $user->accessToken()->create([
  'service_id' => $service['data']['id'],
  'access_token' => $access_token['access_token'],
  'refresh_token' => $access_token['refresh_token'],
  'expires_at' => now()->addSecond($access_token['expires_in'])
 ]);

} //fin del if(! $user->accessToken)

...

Este código que figura dentro del if, puede llevarse a un Trait, para reutilizar el código que se volverá a usar en el registro de usuario. Tenemos un ejemplo de cómo crear un Trait para reutilizar código.

Entoces el contenido del if quedaría así

...

 if(! $user->accessToken){
  $this->getAccessToken($user, $service);
 }

...

Finalmente ya solo queda loguear al usuario guardando el checkbox del formulario de login, y reenviarlo a la ruta deseada. Por defecto irá a la Dashboard que va definida en el fichero AuthenticatedSessionController.php

app\Http\Controllers\Auth\AuthenticatedSessionController.php

...
 
 Auth::login($user, $request->remember);

 return redirect()->intended(RouteServiceProvider::HOME);

Si todo va bien, un usuario nuevo al loguearse ha generado un registro en la base de datos del cliente en la tabla accessToken con el token generado. Si el usuario ya está registrado, se va al panel de control de la web.

Modificar el controlador de registro defecto

Para que el cliente se registre en la api, hay que modificar el controlador RegisteredUserController.

app\Http\Controllers\Auth\RegisteredUserController.php

Importar el Facade Http

use Illuminate\Support\Facades\Http;

Modificación método store

Por defecto viene una validación de usuario, pero lo hace en la propia base de datos del cliente. En caso de que no exista en esta, hay que hacer la comprobación en la base de datos del la API, por lo que hay que apadir esa comprobación.

Si no existe en la base de datos del cliente devuelve un error 422, y hay que capturarlo para hacer la comprobación. Por tanto añadir el código después de la validación pro defecto

Si da error, volver a la página de login indicando el error.

$response = Http::withHeaders([
  'Accept' => 'application/json'
  ])->post('http://api.test/v1/register') . 'register', $request->all());

 if($response->status() == 422){
  return back()->withErrors($response->json()['errors']);
 }

si continua en este punto recupera la info del usuario y se guarda en una variable.

se modifica el código por defecto de la llamada Hash::make quitando la línea del password, pues no se va a guardar en este momento.

...
 $service = $response->json();


        //eliminar para no guardar contraseña 'password' => Hash::make($request->password),
        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
        ]);
...

En este punto se volvería a poner el mismo código que anteriormente, para reusarlo, pero conseguir un código más óptimo, generamos un Trait, que puede ser llamado desde cualquier parte de la aplicación.

Mira como crear un Trait en Laravel de forma fácil.

Importar el trait e indicar su uso dentro de la clase. A continuación colocarlo donde proceda.

...

use App\Traits\Token;

class RegisteredUserController extends Controller
{
 use Token;

...

 $this->getAccessToken($user, $service);

...

Finalizar con el código por defecto del método.

...

 event(new Registered($user));

 Auth::login($user);

 return redirect(RouteServiceProvider::HOME);

} //fin de metodo store

Crear credenciales de tipo password

Como recordatorio inicar que generar las credenciales se realiza en el servidor API donde se va a conectar este cliente que hemos creado, ejecutando el comando

php artisan passport:client --password

Copiar las credenciales generadas al fichero .env del cliente y cachear de nuevo.

.env

...

Client ID: 95ba2a63-fd31-409d-a980--------
Client secret: Jr2XoNr8nLyAHqTlfih--------

...

php artisan config:cache
php artisan migrate:fresh

Así ya se podría acceder a la API desde el cliente tipo Password.

Deja una respuesta

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

Subir