Laravel API authorization with JWT

Learn how to manage your API authentication and authorization with JWT in this article and how it works under the hood. We will use jwt-auth package to get this done.

How does JWT actually work?

JWT does not store the access token. What it actually does is the access token actually contains the information called payload. JWT uses a special algorithm to encrypt the payload and sign a digital signature with a secret key and a payload into a signature combined together into an access token. When an API request is authorized by the access token it will then decrypt the access token payload giving back the information that usually contains the user id and verifying the validity of the token with a digital signature.

Install jwt-auth dependency

The command here will install the jwt-auth package into your Laravel application.

composer require tymon/jwt-auth

Generate the JWT config file

This will generate a config/jwt.php that stores all the configurations of JWT, especially the JWT secret key.

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Generate JWT secret key

Now generate the JWT secret key by the below command. The JWT will use this key to sign the payload in order to generate the access token for your users.

php artisan jwt:secret

Integrate JWT into the user model

To integrate this JWT package into your user model you need to implement JWTSubject which required 2 methods getJWTIdentifier and getJWTCustomClaims to be implemented.

  • getJWTIdentifier is what gives the jwt-auth the primary key value of the user model, which JWT used as a payload to encrypt and give back the access token when login.
  • getJWTCustomClaims is the same as the getJWTIdentifier but this one you can return any custom key value array which JWT will use as a payload with the value given by the getJWTIdentifier above to encrypt into an access token.

For example, if we return this sample associative array in the user model.

public function getJWTCustomClaims()
{
    return ['customKey' => 'customValue'];
}

We can get the value back when the API request with the access token that was created with this custom claim.

auth()->payload()->get('customKey');

In ./app/Models/User.php

<?php

namespace App\Models;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'email_verified_at'
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }

    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = Hash::make($value);
    }
}

Configure auth guard

Config API auth to use JWT and users as a provider. Then set the API guard as default.

In ./config/auth.php

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

If you don’t set the “api” guard as default you need to call the auth() with auth(‘api’) in the AuthController to let auth know to use “api” guard.

AuthController implementation

The __construct middleware will protect the controller with the constructed middleware with specific except login and register as sample below.

In ./app/Http/Controllers/AuthController.php

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login', 'register']]);
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['email', 'password']);

        if (! $token = auth()->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(auth()->user());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth()->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    public function register(Request $request) 
    {
        User::create($request->only('name', 'email', 'password'));
        return response()->json(['message' => 'Successfully register user']);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(auth()->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth()->factory()->getTTL() * 60
        ]);
    }
}

API Route

In ./routes/api.php

Route::group([
    'prefix' => 'auth'
], function () {
    Route::post('login', [AuthController::class, 'login']);
    Route::post('logout', [AuthController::class, 'logout']);
    Route::post('refresh', [AuthController::class, 'refresh']);
    Route::post('register', [AuthController::class, 'register']);
    Route::post('me', [AuthController::class, 'me']);
});

Conclusion

After going through all of this you get the Laravel API authorization with JWT and a clear understanding of how JWT works. These are the steps that we going you through:

  • Install jwt-auth dependency
  • Generate the JWT config file
  • Generate JWT secret key 
  • Integrate JWT into the user model
  • Configure auth guard
  • AuthController implementation
  • API Route