Recently, Laravel changed some composer dependencies, so the framework comes with a JSON Web Token package included by default. Since we have the package to use JWT, let’s make a custom authentication guard for APIs using JSON Web Token.
Getting Started
If you are not familiar with JSON Web Tokens, you can easily read more on the topic. It worths to dig deeper since this solution is often used in web applications. In this post, we do not talk about JWTs in general, nor the anatomy or the structure of the claims or the signature. We assume you are familiar with JWTs, so we can focus on the custom authentication guard to extend Laravel.
Recently, Laravel moved the nexmo and slack notification channels to their package. Now, nexmo’s client pulls the lcobucci/jwt package as well. It means these notification packages come by default with Laravel; we have this JWT package by default as well.
The Custom Authentication Flow
Before we start to code, let’s clear our authentication flow. Usually, we use hybrid applications, that means we have the standard page reload HTTP request to navigate to a page. Also, we use AJAX requests to perform HTTP requests behind the scenes.
Let’s say, the user logs in, and we generate a token with the help of a middleware. We store the token in the session, then print it in a meta tag. If you would use a 100% SPA your approach would be different; maybe you would send an AJAX request to get the token, then store it in the localStorage. For now, it’s good for us.
So, for every AJAX request we send the JWT as a Bearer token in the header, and behind the scenes, our JWT guard authenticates the user. If the authentication is successful, then the user can continue the action it started. Otherwise, the guard aborts the request.
The Token Generator Middleware
First of all, let’s take a look at how to generate JWTs with the help of a middleware. We can create the middleware with the php artisan make:middleware command. Let’s name it to GenereateJwt.
<?php namespace App\Http\Middleware; use Closure; use Lcobucci\JWT\Parser; use Lcobucci\JWT\Builder; use Lcobucci\JWT\ValidationData; use Lcobucci\JWT\Signer\Hmac\Sha256; class GenerateJwt { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param int|null $minutes * @return mixed */ public function handle($request, Closure $next, $minutes = null) { if (! $request->user() && session()->has('token')) { session()->forget('token'); } elseif ($request->user() && (! session('token') || ! $this->validate($request, session('token')))) { session()->put('token', $this->issue($request, $minutes)); } return $next($request); } /** * Issue the token. * * @param \Illuminate\Http\Request $request * @param int|null $minutes * @return string */ protected function issue($request, $minutes) { $builder = (new Builder) ->setId(str_random()) ->setIssuer($request->getHost()) ->setAudience($request->getHost()) ->setSubject($request->user()->id); if ($minutes) { $builder->setExpiration(time() + ($minutes * 60)); } return (string) $builder->sign(new Sha256, config('app.key'))->getToken(); } /** * Validate the token. * * @param \Illuminate\Http\Request $request * @param string $token * @return bool */ protected function validate($request, $token) { $token = (new Parser)->parse($token); $data = new ValidationData; $data->setIssuer($request->getHost()); $data->setAudience($request->getHost()); $data->setSubject($request->user()->id); return $token->validate($data); } }
So, what’s happening here exactly? First of all, we generate the token if the user is authenticated but no token in the session. It means, it just logged in. We use the issue() method to build the token based on the current request. Also, the middleware allows setting an expiration for the token so that we can set the expiration time of the token in minutes. Also, if the token is invalid – we determine it in the validate() method – possibly because it’s expired, a new token is generated and set in the session.
If there is no user, but the token is set – that means the user just logged in – we remove the token from the session. We can use the session here because we still use sessions as the web interface’s authentication driver. That’s we can generate a new token if it’s expired, while the user is authenticated since the session is alive.
We can register the middleware in the $routeMiddleware array:
... 'jwt.generate' => \App\Http\Middleware\GenerateJwt::class, ...
Then we can use it wherever we want. It’s suggested to add it to the web middleware group because whenever you navigate across pages, behind the scenes the token is getting handled.
... 'jwt.generate', // OR 'jwt.generate:30', ...
Place and Use the Token
So, we have the token, but where should we place it? Also, how can we use it in an AJAX call? We can put the following section to the HTML <header> tag to print the token.
@auth <meta name="api-token" content="{{ session('token') }}"> @endauth
We can see, if the user is authenticated, we place the meta tag containing the generated token. From now, we can use it in JavaScript as well. Let’s say we use the axios library for handling AJAX calls.
if (document.head.querySelector('meta[name="api-token"]')) { axios.defaults.headers.common['Authorization'] = 'Bearer ' + document.head.querySelector('meta[name="api-token"]').content; }
This way, we set up the Authorization header for axios, that contains the JWT as a bearer token.
The JWT Guard
By default, Laravel provides a static API token guard, so we can use that as a starting point of our guard, especially since we have a very similar approach.
First, let’s make the JwtGuard class at the app/Auth directory. Based on the built-in TokenGuard, we can write a light-weight JSON Web Token authenticator. Take a look on the code first; then we explain what is what.
<?php namespace App\Auth; use Lcobucci\JWT\Parser; use Illuminate\Http\Request; use InvalidArgumentException; use Lcobucci\JWT\ValidationData; use Illuminate\Auth\GuardHelpers; use Lcobucci\JWT\Signer\Hmac\Sha256; use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\UserProvider; class JwtGuard implements Guard { use GuardHelpers; /** * The request instance. * * @var \Illuminate\Http\Request */ protected $request; /** * The name of the query string item from the request containing the API token. * * @var string */ protected $key; /** * Create a new authentication guard. * * @param \Illuminate\Contracts\Auth\UserProvider $provider * @param \Illuminate\Http\Request $request * @param string $key * @return void */ public function __construct(UserProvider $provider, Request $request, $key = 'api_token') { $this->key = $key; $this->request = $request; $this->provider = $provider; } /** * Get the currently authenticated user. * * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function user() { if (! is_null($this->user)) { return $this->user; } try { $token = (new Parser)->parse($this->getTokenForRequest()); $data = new ValidationData; $data->setIssuer($token->getClaim('iss')); $data->setAudience($token->getClaim('aud')); $data->setSubject($token->getClaim('sub')); if (! $token->verify(new Sha256, config('app.key')) || ! $token->validate($data)) { return; } return $this->user = $this->provider->retrieveById($token->getClaim('sub')); } catch (InvalidArgumentException $exception) { return; } } /** * Get the token for the current request. * * @return string */ public function getTokenForRequest() { $token = $this->request->query($this->key); if (empty($token)) { $token = $this->request->input($this->key); } if (empty($token)) { $token = $this->request->bearerToken(); } if (empty($token)) { $token = $this->request->getPassword(); } return $token; } /** * Validate a user's credentials. * * @param array $credentials * @return bool */ public function validate(array $credentials = []) { if (empty($credentials['id'])) { return false; } if ($this->provider->retrieveById($credentials['id'])) { return true; } return false; } }
First of all, it’s visible we implement the Illuminate\Contracts\Auth\Guard contract, with the help of the Illuminate\Auth\GuardHelpers trait. Key is in the user() method. We just copied the getTokenForRequest() method from the default TokenGuard class, and we use it here as well, to extract the JWT sent with the request.
So what happens exactly? If the token’s format is acceptable for the Parser, it parses the token an returns with a token instance. Then, with the help of the ValidationData class, we can build the data we want to validate. First of all, we verify the token, that means we check if the signature is valid. Then we validate the token, which means we check if the token is not expired, or if it can be used already.
If everything goes well, we extract the subject claim’s value from the token – that contains the user’s ID – and pass it to the UserProvider instance. If it returns with a User instance, the authentication was successful.
Registering the Guard
Laravel provides a nice way to extend its authentication layer. You can find the documentation here, about adding the guard to the AuthServiceProvider‘s boot() method.
// Register the JWT Guard Auth::extend('jwt', function ($app, $name, array $config) { return new JwtGuard(Auth::createUserProvider($config['provider']), $app['request']); });
Also, we need to update the configuration as well. At the auth.php config file, we need to update the API guard settings. Just set the token driver to jwt.
'guards' => [ ... 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],
From now, whenever you use the auth:api middleware, the JwtGuard will be used instead of the default TokenGuard.
Summary
This is just a basic representation of JWT handling with Laravel. Of course, there are lots of things that you may consider to use or change for your own project.
If you are not familiar with JWTs at all, we strongly suggest diving a bit deeper in the topic, because it’s a nice way to authenticate requests across web and API interfaces.