Código en GitHub de REST API in Laravel with Sanctum

Montaje de ficheros en el droplet:

sshfs usuario@alumnoportada.co.es:/home/usuario/todo/ servidor -p 22

Conectarse por ssh:

ssh paco@alumnoportada.com.es

Situarse en la carpeta del proyecto:

cd todo

Instalar  Sanctum

composer require laravel/sanctum

Publicar la configuración de sanctum:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Modificación del api en el fichero app/Http/Kernel.php:

protected $middlewareGroups = [
...
...
    'api' => [
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
...
...
];

Fichero app/Http/Kernel.php:

<?php

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 = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::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,
            \Laravel\Jetstream\Http\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];
}

Modificar el fichero app/Models/Task.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;

class Task extends Model
{
    use HasFactory;
    
    protected $fillable = [
        'description'
    ];

    public function user()
    {
    	return $this->belongsTo(User::class);
    }
}

Ejecutar la migración (aunque no es necesario).

php artisan migrate

Crear los recursos del API

php artisan make:resource Task

Modificar el fichero

nano app/Http/Resources/Task.php

poner este contenido:

<?php
  
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;

class Task extends JsonResource
{

    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'description' => $this->description,
            'created_at' => $this->created_at->format('d/m/Y'),
            'updated_at' => $this->updated_at->format('d/m/Y'),
        ];
    }
    
}

Crear los controladores

Crear el fichero app/Http/Controllers/API/BaseController.php

mkidr app/Http/Controllers/API
nano app/Http/Controllers/API/BaseController.php

poner este contenido

<?php


namespace App\Http\Controllers\API;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as Controller;


class BaseController extends Controller
{
    /**
     * success response method.
     *
     * @return \Illuminate\Http\Response
     */
    public function sendResponse($result, $message)
    {
    	$response = [
            'success' => true,
            'data'    => $result,
            'message' => $message,
        ];

        return response()->json($response, 200);
    }


    /**
     * return error response.
     *
     * @return \Illuminate\Http\Response
     */
    public function sendError($error, $errorMessages = [], $code = 404)
    {
    	$response = [
            'success' => false,
            'message' => $error,
        ];

        if(!empty($errorMessages)){
            $response['data'] = $errorMessages;
        }

        return response()->json($response, $code);
    }
}

Crear el fichero app/Http/Controllers/API/AuthController.php

nano app/Http/Controllers/API/AuthController.php

poner este contenido

<?php
   
namespace App\Http\Controllers\API;
   
use Illuminate\Http\Request;
use App\Http\Controllers\API\BaseController as BaseController;
use Illuminate\Support\Facades\Auth;
use Validator;
use App\Models\User;
   
class AuthController extends BaseController
{
    public function signin(Request $request)
    {
        if(Auth::attempt(['email' => $request->email, 'password' => $request->password])){ 
            $authUser = Auth::user(); 
            $success['token'] =  $authUser->createToken('MyAuthApp')->plainTextToken; 
            $success['name'] =  $authUser->name;
   
            return $this->sendResponse($success, 'User signed in');
        } 
        else{ 
            return $this->sendError('Unauthorised.', ['error'=>'Unauthorised']);
        } 
    }
    public function signup(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email',
            'password' => 'required',
            'confirm_password' => 'required|same:password',
        ]);
   
        if($validator->fails()){
            return $this->sendError('Error validation', $validator->errors());       
        }
   
        try {
            $input = $request->all();
            $input['password'] = bcrypt($input['password']);
            $user = User::create($input);
            $success['token'] =  $user->createToken('MyAuthApp')->plainTextToken;
            $success['name'] =  $user->name;
       
            return $this->sendResponse($success, 'User created successfully.');
        } catch (\Exception $e) {
            return $this->sendError('Registration Error' , $e->getMessage());
        }
    }
    public function logout(Request $request)
    {
        
        // Get user who requested the logout
        $user = request()->user(); //or Auth::user()
        // Revoke current user token
        $user->tokens()->where('id', $user->currentAccessToken()->id)->delete();
        $success['name'] =  $user->name;
        // return response()->json(['message' => 'User successfully signed out']);
        return $this->sendResponse($success, 'User successfully signed out.');
    }
   
}

 

TODO:

– validar el email cuando un usuario se registra a través del API

(enviar un email de validación al registrarse, la validación posterior se haría desde el navegador)

API

Using HTTP Methods for RESTful Services

Crear el fichero de rutas routes/api.php

nano routes/api.php

poner este contenido:

<?php
  
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
  
use App\Http\Controllers\API\AuthController;
use App\Http\Controllers\API\TaskController;

use App\Http\Controllers\API\MailController;
  
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
*/
  
Route::post('login', [AuthController::class, 'signin']);
Route::post('register', [AuthController::class, 'signup']);
     
Route::middleware('auth:sanctum')->group( function () {
    Route::resource('tasks', TaskController::class);
    Route::post('logout', [AuthController::class, 'logout']);
    Route::post('email', [MailController::class, 'sendEmail']);       
});

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Crear el fichero app/Http/Controllers/API/TaskController.php y añadir las operaciones CRUD en el API

nano app/Http/Controllers/API/TaskController.php

poner este contenido:

<?php
   
namespace App\Http\Controllers\API;
   
use Illuminate\Http\Request;
use App\Http\Controllers\API\BaseController as BaseController;
use Validator;
use App\Models\Task;
use App\Http\Resources\Task as TaskResource;
   
class TaskController extends BaseController
{

    public function index()
    {
        $tasks = Task::where('user_id', auth()->user()->id)
               ->get();
        return $this->sendResponse(TaskResource::collection($tasks), 'Tasks fetched.');
    }

    
    public function store(Request $request)
    {
        $input = $request->all();
        $validator = Validator::make($input, [
            'description' => 'required'
        ]);
        
        if ($validator->fails()){
            return $this->sendError($validator->errors());       
        }
        
        $task = new Task();
    	$task->description = $request->description;
    	$task->user_id = auth()->user()->id;
    	$task->save();
        
        return $this->sendResponse(new TaskResource($task), 'Task created.');
    }

   
    public function show($id)
    {
        $task = Task::find($id);
        
        if (is_null($task)) {
            return $this->sendError('Task does not exist.');
        }
        return $this->sendResponse(new TaskResource($task), 'Task fetched.');
    }
    

    public function update(Request $request, Task $task)
    {
        $input = $request->all();

        $validator = Validator::make($input, [
            'description' => 'required'
        ]);

        if($validator->fails()){
            return $this->sendError($validator->errors());       
        }

        $task->description = $input['description'];
        //$task->description = $request->description;
        $task->save();
        
        return $this->sendResponse(new TaskResource($task), 'Task updated.');
    }
   
    public function destroy($id)
    {
        $task = Task::find($id);
        if (is_null($task)) {
            return $this->sendError('Task does not exist.', 'Task NOT deleted.');
        }
        else {
            $task->delete();
            return $this->sendResponse([], 'Task deleted.');
        }
    }
}

Crear el fichero

nano app/Http/Controllers/API/MailController.php

poner este contenido:

<?php

namespace App\Http\Controllers\API;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Http\Controllers\API\BaseController as BaseController;
use App\Mail\NewEmail;
use Validator;
use Exception;

class MailController extends BaseController
{	 
    public function sendEmail(Request $request) {
        $validator = Validator::make($request->all(), [
            'to' => 'required|email',
            'from' => 'required|email',
            'subject' => 'required',
            'message' => 'required',
        ]);
        
        if ($validator->fails()) {
            // $status = 501;
            // return response()->json( ['status' => false, 'message' => $validator->errors()->getMessages()], 501);
            return $this->sendError('Error validation', $validator->errors()->getMessages()); 
        } else {                                           
                try {
                    $email =  $request->json()->all();
                    $to = $email['to'];
                    $from = $email['from'];
                    $subject = $email['subject'];
                    $message = $email['message'];

                    $data = [
                        'subject' => $subject,
                        'to' => $to,
                        'from' => $from,
                        'message' => $message
                        ];
                    
                    Mail::send('emails.send', $data, function($message) {
                            $message->to('paco.portada@protonmail.com')->subject('hola');
                            $message->from('emisor@gmail.com');
                            });
                            
                    //Mail::to('paco.portada@protonmail.com')->send(new NewEmail($from, $subject, $message));
                    // $status = 200;
                    return $this->sendResponse('', 'Email has been sent to ' . $data['to']);
                } catch (Exception $exception) {
                    // $status = 554;
                    return $this->sendError('', 'Email has not been sent. Error: ' . $exception->getMessage());
                }
            }

      //return response()->json($message, $status);
    }
}

Crear el fichero resources/views/emails/send.blade.php

<!doctype html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
    <title>Nuevo mensaje</title>
</head>
<body>
    <p>Mensaje:</p>
<h3>Hola, es un mensaje de prueba</h3>
    
</body>
</html>

Duración del token:

How does Laravel sanctum expire tokens?

Configuración en el fichero config/sanctum.php

/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. If this value is null, personal access tokens do
| not expire. This won't tweak the lifetime of first-party sessions.
|
*/

//'expiration' => null,
//'expiration' => 60 * 24 * 7,
'expiration' => 60,