I noticed that one change in the alpha version is errors now throw a Symfony-type exception rather than returning a json response.
Example: https://github.com/tymondesigns/jwt-auth/blob/develop/src/Http/Middleware/BaseMiddleware.php#L45
If I'm using this with Lumen, do I need to add global exception handlers for all error classes? If so, do you have an example handy that I can copy?
Any update on this? I'm running into the same problem, instead of JSON we get the exception when specifying \Tymon\JWTAuth\Http\Middleware\Authenticate as route middleware (using before).
Any way to restore the previous behavior?
The previous behavior was dropped because it turned out there were many cases where people didn't want responses and using exceptions was a more general solution. Laravel 5+'s Handler also supports exception-based control flow a lot better (and encourages it some extent), so paralleling that is a bit more natural.
You can use global capture if you want. Here's an example I'm using. Inside Handler->render:
if ($e instanceof \Symfony\Component\HttpKernel\Exception\BadRequestHttpException) {
return $this->TokenMissing();
}
if ($e instanceof \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException) {
// TODO: Maybe writing our own middleware for this would be better
// than trying to distinguish between them based only upon strings.
if ($e->getMessage() === 'Token has expired') {
// This case must be distinguished because our front-end runs a
// "refresh-as-necessary" approach using expiration as a trigger
return $this->TokenExpired();
} else {
return $this->TokenInvalid();
}
}
Then the convenience methods within Handler are all something like this:
private function TokenExpired()
{
return response()->json([
'error' => 'TokenExpired',
'path' => $this->request->path(),
'message' => $this->exception->getMessage(),
], HTTP_UNAUTHORIZED);
}
(Though you could of course do this inline and without the convenience properties $this->request and $this->exception.)
Of course, read my todo note in there... it's probably safer to check unique exception types (like the JWTException subclasses) in case someday something unexpected throws a Symfony exception and you assume it's a token thing. But changing the exceptions would require custom middleware, ad if you're doing that, you might just want it to return the responses directly and avoid the global handler altogether. Just swap out the exceptions in BaseMiddleware and its children with the responses you want and save them as new middlewares.
Thank you for the feedback. Do you suggest modifying BaseMiddleware of jwt-auth or deriving from it?
I would just use it as a template to copy from and make a brand new class that doesn't formally inherit or override it.
This works fine without much code duplication:
<?php
namespace App\Http\Middleware;
use Closure;
use Tymon\JWTAuth\Http\Middleware\Authenticate;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
/*
* We override the standard class to return a JSON response instead
* of a Symfony exception
*/
class JwtAuthenticate extends Authenticate
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
try {
$this->authenticate($request);
return $next($request);
} catch(BadRequestHttpException $e) {
return response()->json([
'error' => 'token_not_provided',
'path' => $request->path(),
'message' => $e->getMessage(),
], 400);
} catch (UnauthorizedHttpException $e) {
return response()->json([
'error' => 'unauthorized',
'path' => $request->path(),
'message' => $e->getMessage(),
], 401);
}
}
}
I had to patch the throttle middleware due to a Laravel/Lumen bug, see here: https://github.com/laravel/framework/issues/12309
Updated to also handle UnauthorizedHttpException
Looks good! I assume you know it can still throw an UnauthorizedHttpException when the token is invalid, expired, or blacklisted?
Most helpful comment
The previous behavior was dropped because it turned out there were many cases where people didn't want responses and using exceptions was a more general solution. Laravel 5+'s
Handleralso supports exception-based control flow a lot better (and encourages it some extent), so paralleling that is a bit more natural.You can use global capture if you want. Here's an example I'm using. Inside
Handler->render:Then the convenience methods within
Handlerare all something like this:(Though you could of course do this inline and without the convenience properties
$this->requestand$this->exception.)Of course, read my todo note in there... it's probably safer to check unique exception types (like the
JWTExceptionsubclasses) in case someday something unexpected throws a Symfony exception and you assume it's a token thing. But changing the exceptions would require custom middleware, ad if you're doing that, you might just want it to return the responses directly and avoid the global handler altogether. Just swap out the exceptions inBaseMiddlewareand its children with the responses you want and save them as new middlewares.