Hướng dẫn laravel sanctum

  • December 4, 2022
  • 1680

Laravel Sanctum

Laravel sanctum giúp quản lý và tạo các api token phục vụ auth cho single page applications(SPA). Sanctum cho phép mỗi người dùng tạo nhiều api token cho tài khoản của họ. Ứng dụng của bạn có thể phân quyền theo các token này.

Install laravel sanctum

Để cài đặt Laravel Sanctum bạn cần sử dụng composer


composer require laravel/sanctum

public laravel sanctum cho dự án của bạn


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

Tạo table để quản lý các token đăng nhập


php artisan migrate

// Các token được quản lý trong bảng personal_access_tokens
mysql> desc personal_access_tokens;
+----------------+---------------------+------+-----+---------+----------------+
| Field          | Type                | Null | Key | Default | Extra          |
+----------------+---------------------+------+-----+---------+----------------+
| id             | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| tokenable_type | varchar(255)        | NO   | MUL | NULL    |                |
| tokenable_id   | bigint(20) unsigned | NO   |     | NULL    |                |
| name           | varchar(255)        | NO   |     | NULL    |                |
| token          | varchar(64)         | NO   | UNI | NULL    |                |
| abilities      | text                | YES  |     | NULL    |                |
| last_used_at   | timestamp           | YES  |     | NULL    |                |
| expires_at     | timestamp           | YES  |     | NULL    |                |
| created_at     | timestamp           | YES  |     | NULL    |                |
| updated_at     | timestamp           | YES  |     | NULL    |                |
+----------------+---------------------+------+-----+---------+----------------+
10 rows in set (0.01 sec) 

Thêm auth config


'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'sanctum',
            'provider' => 'users',
            'hash' => false,
        ],
 ],

Thêm middleware của laravel sanctum vào nhóm api middleware.


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

Thêm middleware add accept application/json to header khi sửa dụng api, giúp laravel sanctum có thể check được token invalid khi sử dụng api.


<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

/**
 * Add accept to header
 * the sanctum middleware
 */
class AddAcceptToHeader
{
    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return Response|RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        if ($request->is('*api*')) {
            $request->headers->set('Accept', 'application/json');
        }
        
        return $next($request);
    }
}

Thêm middleware vào nhóm api middleware


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

Như vậy là chúng ta đã cài đặt và thiết lập thành công laravel sanctum, giờ đến sử dụng nó.

How to use laravel sanctum

Tạo route cho chức năng đăng kí, đăng nhập, user profile, logout.


<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\V1\AuthController;

Route::group(['prefix' => 'v1'], function () {
    Route::post('register', [AuthController::class, 'createUser']);
    Route::post('login', [AuthController::class, 'login']);

    Route::middleware(['auth:sanctum'])->group(function () {
        Route::post('logout', [AuthController::class, 'logout']);
        Route::get('profile', [AuthController::class, 'profile']);
    });
});

Tạo AuthController đăng kí, đăng nhập, lấy thông tin user và logout.


<?php

namespace App\Http\Controllers\Api\V1;

use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\CreateUserRequest;
use App\Http\Requests\Auth\AuthRequest;
use App\Http\Resources\AuthResource;
use App\Http\Resources\Users\UserResource;
use App\Http\Resources\SuccessResource;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    /**
     * Register user
     * 
     * @OA\Post(
     *      path="/api/v1/register",
     *      tags={"Auth"},
     *      @OA\RequestBody(
     *         required=true,
     *         @OA\JsonContent(
     *              ref="#/components/schemas/CreateUserRequest"
     *         )
     *      ),
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\JsonContent(ref="#/components/schemas/AuthResource")
     *       ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     * )
     * @param CreateUserRequest $request
     * @return AuthResource 
     */
    public function createUser(CreateUserRequest $request): AuthResource
    {
        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password)
        ]);

        return new AuthResource($user);
    }

    /**
     * User login
     * 
     * @OA\Post(
     *      path="/api/v1/login",
     *      tags={"Auth"},
     *      @OA\RequestBody(
     *         required=true,
     *         @OA\JsonContent(
     *              ref="#/components/schemas/AuthRequest"
     *         )
     *      ),
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\JsonContent(ref="#/components/schemas/AuthResource")
     *       ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     * )
     * 
     * @param AuthRequest $request
     * @return AuthResource 
     */
    public function login(AuthRequest $request): AuthResource
    {
        if (!Auth::attempt($request->only('email', 'password'))) {
            throw ApiException::badRequest('username or password wrong');
        }

        $user = User::where('email', $request['email'])->firstOrFail();

        return new AuthResource($user);
    }

    /**
     * User profile
     * 
     * @OA\Get(
     *      path="/api/v1/profile",
     *      tags={"Auth"},
     *      security={{ "sanctum": {} }},
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\JsonContent(ref="#/components/schemas/UserResource")
     *       ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     * )
     * 
     * @param Request $request
     * @return UserResource 
     */
    public function profile(Request $request): UserResource
    {
        return new UserResource($request->user());
    }

    /**
     * User logout
     * 
     * @OA\Post(
     *      path="/api/v1/logout",
     *      tags={"Auth"},
     *      security={{ "sanctum": {} }},
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\JsonContent(ref="#/components/schemas/SuccessResource")
     *       ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     * )
     * 
     * @return SuccessResource 
     */
    public function logout(): SuccessResource
    {
        $user = auth()->guard('api')->user();
        if (method_exists($user->currentAccessToken(), 'delete')) {
            $user->currentAccessToken()->delete();

            return new SuccessResource('Logout success');
        }
        
        auth()->guard('web')->logout();

        return new SuccessResource('Logout success');
    }
}

Tạo CreateUserRequest


<?php

namespace App\Http\Requests\Auth;

use App\Http\Requests\BaseRequest;

/**
 * @OA\Schema(
 *      properties={
 *          @OA\Property(property="name", type="string", example="huunv"),
 *          @OA\Property(property="email", type="string", example="huunv@gmail.com"),
 *          @OA\Property(property="password", type="string", example="123456"),
 *      }
 * )
 */
class CreateUserRequest extends BaseRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            'name' => 'required',
            'email' => 'required|email|unique:users,email',
            'password' => 'required'
        ];
    }
}

Tạo AuthRequest


<?php

namespace App\Http\Requests\Auth;

use App\Exceptions\ApiException;
use App\Http\Requests\BaseRequest;
use Illuminate\Contracts\Validation\Validator;

/**
 * @OA\Schema(
 *      properties={
 *          @OA\Property(property="email", type="string", example="huunv@gmail.com"),
 *          @OA\Property(property="password", type="string", example="123456"),
 *      }
 * )
 */
class AuthRequest extends BaseRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            'email' => 'required|email',
            'password' => 'required'
        ];
    }
}

Tạo AuthResource thiết lập response trả về cho api. Tìm hiểu thêm hướng dẫn sử dụng laravel resource


<?php

namespace App\Http\Resources;

/**
 * @OA\Schema(
 *      properties={
 *          @OA\Property(
 *              property="success",
 *              type="boolean",
 *              example="true"
 *          ),
 *          @OA\Property(
 *              property="data",
 *              type="object",
 *                  @OA\Property(property="token", type="string", example="3|PXX3pewsSBbtJhJQXXuRZ1NiLHzTvD6Bv2TBUJjm"),
 *                  @OA\Property(property="token_type", type="string", example="Bearer"),
 *          ),
 *          @OA\Property(
 *              property="message",
 *              type="string",
 *              example="success"
 *          )
 *      }
 * )
 */
class AuthResource extends BaseResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
     */
    public function toArray($request)
    {
        return [
            'token' => $this->createToken("api")->plainTextToken,
            'token_type' => 'Bearer',
        ];
    }
}

Check api with posman

Kiểm tra api đăng kí user
Register user
Api đăng nhập, mỗi lần đăng nhập laravel sanctum sẽ tạo ra 1 token mới
Login with laravel sanctum

Laravel sanctum create auth token

Laravel sanctum tạo api token.

// Create token without permission
$user->createToken('token-name')->plainTextToken;

// Create Abilities token
return $user->createToken('token-name', ['server:update'])->plainTextToken;

// Check ability using
if ($user->tokenCan('server:update')) {
    //
}

Revoking token

Xoá token

// Revoke all tokens...
$user->tokens()->delete();
 
// Revoke the token that was used to authenticate the current request...
$request->user()->currentAccessToken()->delete();
 
// Revoke a specific token...
$user->tokens()->where('id', $tokenId)->delete();

Token expired

Mặc định, token có thời gian sống là mãi mãi, để thiết lập thời gian token hết hạn setting trong file 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,

Tổng kết

Như vậy là chúng ta đã cài đặt và sử dụng thành công laravel sanctum. Tạo được simple token cho ứng dụng rồi nhé.