# Laravel Passport JWT/Password Grant Authentication
This is the backend implementation of the following blogpost from Hasura - [The Ultimate Guide to handling JWTs on frontend clients](https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/).
- **Laravel Version:** v6.10.1
- **Passport Version:** v8.2.0
---
**Assumed knowledge:**
- [Laravel](https://laravel.com/docs/6.x)
- [Passport](https://laravel.com/docs/6.x/passport)
- Basic JWT authentication knowledge (read the [blog post](https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/) above)
- Basic [Http middleware](https://laravel.com/docs/6.x/middleware) knowledge
## Table of Content
[ToC]
## Basic MVP
### Register api routes
```php
// routes/api.php
Route::post('/login', 'Api\LoginController');
Route::post('/logout', 'Api\LogoutController');
Route::post('/keep-login', 'Api\KeepLoginController');
```
### Add cookie middlewares to api routes
```php
// app/Http/Kernel.php
class Kernel extends HttpKernel {
// ...
protected $middlewareGroups = [
// ...
'api' => [
'throttle:60,1',
// Add this
\App\Http\Middleware\EncryptCookies::class,
// and this. Not sure if this is required.
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
'bindings',
],
];
// ...
}
```
### Configure Passport's token and refresh token lifetime
```php
// app/Providers/AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider {
// ...
public function boot() {
$this->registerPolicies();
// Add these 2 lines
Passport::tokensExpireIn(now()->addMinutes(15));
Passport::refreshTokensExpireIn(now()->addMinutes(15));
}
}
```
### Create Api\LoginController
This is our wrapper around Passport's AccessTokenController
```php
// app/Http/Controllers/API/LoginController.php
namespace App\Http\Controllers\API;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use Psr\Http\Message\ServerRequestInterface;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class LoginController extends Controller {
public function __invoke(
ServerRequestInterface $accessTokenRequest,
AccessTokenController $accessTokenIssuer,
Request $request
) {
// Inject password grant_type.
// This can be refactor to middleware.
$request->request->add([
'grant_type' => 'password',
]);
// Inject client id and secret.
// This can be refactor to middleware.
$request->request->add([
'client_id' => 'YOUR_CLIENT_ID',
'client_secret' => 'YOUR_CLIENT_SECRET',
'scope' => '*',
]);
// Main logic
$response = $accessTokenIssuer->issueToken(
$accessTokenRequest->withParsedBody(
$request->validate([
'grant_type' => 'required|string',
'client_id' => 'required|numeric',
'client_secret' => 'required|string',
'username' => 'required|email|max:255',
'password' => 'required|string',
'scope' => 'required|string',
])
)
);
// Add refresh token cookie to response.
// This can be refactor to midleware.
$refreshToken = json_decode($response->content())->refresh_token ?? '';
if (!empty($refreshToken)) {
$response->cookie('refresh_token', $refreshToken, 15);
}
return $response;
}
}
```
### Create Api\KeepLoginController
```php
// app/Http/Controllers/API/KeepLoginController.php
namespace App\Http\Controllers\API;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use Psr\Http\Message\ServerRequestInterface;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class KeepLoginController extends Controller {
public function __invoke(
ServerRequestInterface $accessTokenRequest,
AccessTokenController $accessTokenIssuer,
Request $request
) {
// Inject refresh token grant_type.
// This can be refactor to middleware.
$request->request->add([
'grant_type' => 'refresh_token',
]);
// Inject refresh token from cookie.
// This can be refactor to middleware.
$request->request->add([
'refresh_token' => $request->cookie('refresh_token'),
]);
// Inject client id and secret.
// This can be refactor to middleware.
$request->request->add([
'client_id' => 'YOUR_CLIENT_ID',
'client_secret' => 'YOUR_CLIENT_SECRET',
]);
// Main logic
$response = $accessTokenIssuer->issueToken(
$accessTokenRequest->withParsedBody(
$request->validate([
'grant_type' => 'required|string',
'client_id' => 'required|numeric',
'client_secret' => 'required|string',
'refresh_token' => 'nullable|string',
'scope' => 'required|string',
])
)
);
// Add refresh token cookie to response.
// This can be refactor to midleware.
$refreshToken = json_decode($response->content())->refresh_token ?? '';
if (!empty($refreshToken)) {
$response->cookie('refresh_token', $refreshToken, 15);
}
return $response;
}
}
```
### Create Api\LogoutController
```php
// app/Http/Controllers/API/LogoutController.php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class LogoutController extends Controller {
public function __contruct() {
$this->middleware([
'auth:api',
]);
}
public function __invoke(Request $request) {
$request->user()->token()->revoke();
return response()
// Remove refresh token cookie from response.
// This can be refactor to middleware.
->cookie('refresh_token', null, -262800)
->noContent();
}
}
```
## Refactor with Middlewares
### Create InjectPasswordGrantType middleware
```php
// app/Http/Middleware/InjectPasswordGrantType.php
namespace App\Http\Middleware;
use Closure;
class InjectPasswordGrantType {
public function handle($request, Closure $next) {
$request->request->add([
'grant_type' => 'password',
]);
return $next($request);
}
}
```
### Create InjectClientIdAndSecret middleware
```php
// app/Http/Middleware/InjectClientIdAndSecret.php
namespace App\Http\Middleware;
use Closure;
class InjectClientIdAndSecret {
public function handle($request, Closure $next) {
$request->request->add([
'client_id' => 'YOUR_CLIENT_ID',
'client_secret' => 'YOUR_CLIENT_SECRET',
'scope' => '*',
]);
return $next($request);
}
}
```
### Create AddRefreshTokenCookieToResponse middleware
```php
// app/Http/Middleware/AddRefreshTokenCookieToResponse.php
namespace App\Http\Middleware;
use Closure;
class AddRefreshTokenCookieToResponse {
public function handle($request, Closure $next) {
$response = $next($request);
refreshToken = json_decode($response->content())->refresh_token ?? '';
if (!empty($refreshToken)) {
$response->cookie('refresh_token', $refreshToken, 15);
}
return $response;
}
}
```
### Refactor Api\LoginController with middlewares
```php
// app/Http/Controllers/API/LoginController.php
namespace App\Http\Controllers\API;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use App\Http\Middleware\AddRefreshTokenCookieToResponse;
use App\Http\Middleware\InjectClientIdAndSecret;
use App\Http\Middleware\InjectPasswordGrantType;
use Psr\Http\Message\ServerRequestInterface;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class LoginController extends Controller {
public function __construct() {
$this->middleware([
// Request middlewares
InjectPasswordGrantType::class,
InjectClientIdAndSecret::class,
// Response middlewares
AddRefreshTokenCookieToResponse::class,
]);
}
public function __invoke(
ServerRequestInterface $accessTokenRequest,
AccessTokenController $accessTokenIssuer,
Request $request
) {
return $accessTokenIssuer->issueToken(
$accessTokenRequest->withParsedBody(
$request->validate([
'grant_type' => 'required|string',
'client_id' => 'required|numeric',
'client_secret' => 'required|string',
'username' => 'required|email|max:255',
'password' => 'required|string',
'scope' => 'required|string',
])
)
);
}
}
```
### Create InjectRefreshTokenGrantType middleware
```php
// app/Http/Middleware/InjectRefreshTokenGrantType.php
namespace App\Http\Middleware;
use Closure;
class InjectRefreshTokenGrantType {
public function handle($request, Closure $next) {
$request->request->add([
'grant_type' => 'refresh_token',
]);
return $next($request);
}
}
```
### Create InjectRefreshTokenFromCookie middleware
```php
// app/Http/Middleware/InjectRefreshTokenFromCookie.php
namespace App\Http\Middleware;
use Closure;
class InjectRefreshTokenFromCookie {
public function handle($request, Closure $next) {
$request->request->add([
'refresh_token' => $request->cookie('refresh_token'),
]);
return $next($request);
}
}
```
### Refactor Api\KeepLoginController with middlewares
```php
// app/Http/Controllers/API/KeepLoginController.php
namespace App\Http\Controllers\API;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use App\Http\Middleware\AddRefreshTokenCookieToResponse;
use App\Http\Middleware\InjectRefreshTokenFromCookie;
use App\Http\Middleware\InjectRefreshTokenGrantType;
use App\Http\Middleware\InjectClientIdAndSecret;
use Psr\Http\Message\ServerRequestInterface;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class KeepLoginController extends Controller {
public function __construct() {
$this->middleware([
// Request middlewares
InjectRefreshTokenGrantType::class,
InjectRefreshTokenFromCookie::class,
InjectClientIdAndSecret::class,
// Response middlewares
AddRefreshTokenCookieToResponse::class,
]);
}
public function __invoke(
ServerRequestInterface $accessTokenRequest,
AccessTokenController $accessTokenIssuer,
Request $request
) {
return $accessTokenIssuer->issueToken(
$accessTokenRequest->withParsedBody(
$request->validate([
'grant_type' => 'required|string',
'client_id' => 'required|numeric',
'client_secret' => 'required|string',
'refresh_token' => 'nullable|string',
'scope' => 'required|string',
])
)
);
}
}
```
### Create RemoveRefreshTokenCookieFromResponse middleware
```php
// app/Http/Middleware/RemoveRefreshTokenCookieFromResponse.php
namespace App\Http\Middleware;
use Closure;
class RemoveRefreshTokenCookieFromResponse {
public function handle($request, Closure $next) {
$response = $next($request);
$response->cookie('refresh_token', null, -262800);
return $response;
}
}
```
### Refactor Api\LogoutController with middlewares
```php
// app/Http/Controllers/API/LogoutController.php
namespace App\Http\Controllers\API;
use App\Http\Middleware\RemoveRefreshTokenCookieFromResponse;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class LogoutController extends Controller {
public function __contruct() {
$this->middleware([
'auth:api',
// Response middlewares
RemoveRefreshTokenCookieFromResponse::class,
]);
}
public function __invoke(Request $request) {
$request->user()->token()->revoke();
return response()->noContent();
}
}
```