Jwt-auth: Add support & documentation for integrating with Lumen 5.2

Created on 8 Jan 2016  路  49Comments  路  Source: tymondesigns/jwt-auth

Since Lumen does not support session state, incoming requests that we wish to authenticate must be authenticated via a stateless mechanism such as API tokens. This package is really going to be helpful for resolving out the authenticated user from the request through API token.
https://lumen.laravel.com/docs/5.2/authentication

Auth::viaRequest('api', function ($request) {
    try {
        if (! $user = JWTAuth::parseToken()->authenticate()) {
            return null;
        }
    } catch (Exception $e) {
        return null;
    } 
    return $user;
});

It would be really helpful if we have another section in the wiki about integrating this package with Lumen. I see that most of the work has already been done ( already there is a LumenServiceProvider ), only that it needs to be upgraded to support v5.2 and documented. Thank you Symon for giving us this wonderful package that we can use with out Laravel and Lumen projects.

Most helpful comment

@tymondesigns It works for me, but requires me to enable both Eloquent and Facades.

Without Facades I can't use the Auth::function() and simply injecting JWTGuard in my constructor fails at building it.

Without Eloquent (using 'database' driver), it fails because GenericUser does not implement JWTSubject.

Is there any way to make this work without either Facades and Eloquent?

@jake142 Add a provider for your guard:

    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users'
        ],
    ],

All 49 comments

@rajabishek Where do we put this Auth::viaRequest function? And what exactly is api here?

Edit: Ah I see that the block is already in the AuthServiceProvider, but I still don't quite understand what the api portion represents.

Edit again: Looks like it's a "driver"

public function viaRequest($driver, callable $callback)
    {
        return $this->extend($driver, function () use ($callback) {
            $guard = new RequestGuard($callback, $this->app['request']);

            $this->app->refresh('request', $guard, 'setRequest');

            return $guard;
        });
    }

Weird, $user always return null for me. It's the null from the if statement, not the catch though.

@syropian The AuthServiceProvider in Lumen 5.2 already has a call to Auth::viaRequest method that accepts a Closure which will be called when the incoming request needs to be authenticated ( if auth middleware is used ). Within this Closure, we may resolve out App\User instance however we wish. If no authenticated user can be found for the request, the Closure should return. So what I was suggesting was if we use this package with Lumen 5.2 then we would have to do something along the lines of

Auth::viaRequest('api', function ($request) {
    try {
        if (! $user = JWTAuth::parseToken()->authenticate()) {
            return null;
        }
    } catch (Exception $e) {
        return null;
    } 
    return $user;
});

And now once this is done, in our controllers or route handling closures we can directly get the authenticated user using the Auth::user() or through $request->user() just like how we would deal with sessions. Something like

use Illuminate\Http\Request;

$app->get('/post/{id}', ['middleware' => 'auth', function (Request $request, $id) {
    $user = Auth::user();
    //or
    $user = $request->user();
}]);

So I just felt there must be a separate section in the wiki that needs to have this documented on how we can integrate and use this package with Lumen apps and specifically with v5.2.

@rajabishek I tried setting mine up just like your example above, but JWTAuth::parseToken()->authenticate() is always null. I verified that the jwt token is present in the request but the authenticate method can never seem to fetch a user. Any ideas?

@syropian If you had used a similar code in the AuthServiceProvider that I had written above, then probably null might be returned because there is some exception beign thrown. Just remove the exception handler and probably we can get to see where the error is. Give this a try.

@rajabishek I actually tried that by just doing a really simple version like so:

Auth::viaRequest('api', function ($request) {
    $user = JWTAuth::parseToken()->authenticate();
    return $user;
});

but it's not returning a user (or a useful error). It's weird because I am able to generate the token for a given user no problem, but just can't get it to work the other way around! :c

@syropian Try doing this.

Auth::viaRequest('api', function ($request) {
    JWTAuth::parseToken();
    $token = JWTAuth::getToken();
    dd($token);
});

Just to run a check whether the package is actually able to grab the token from the request. If $token happens to be null, then we now know that there is some problem with the getting the token from the request headers, if so we can try setting the token explicitly by grabbing the Authorization header first and then the token value from that. Then we can set the token on the JWTAuth instance through the JWTAuth::setToken($token); method. Before doing all this I would recommend to actually check once whether the request has the Authorization header. If there is no Authorization header then it could be a problem with Apache also. Reference: https://github.com/tymondesigns/jwt-auth/issues/200

@rajabishek Yup, I did check for that, the token is indeed present in the header.

Anyway, I changed my code up a bit to ensure it returned null if the user wasn't present, and I get a 401 (unauthorized) response now. Clearly that means null is being returned from Auth::viaRequest(). No idea why.

Auth::viaRequest('api', function ($request) {
  if( !$user = JWTAuth::parseToken()->authenticate() ){
    return null;
  }
  return $user;
});

and in my routes I have:

$app->post('document/create', ['middleware' => 'auth', function(Request $request, $id) {
    $user = Auth::user();
    dd($user);
}]);

I've made sure to enable the auth middleware in my app.php file.

@syropian You have the AuthServiceProvider service provider uncommented in your bootstrap/app.php file right to make sure it is registered ?

@rajabishek yup!

@rajabishek There isn't something internal that relies on the User model being in in the App\User namespace is there? I prefer to keep all my models in App\Models\Whatever namespace. I've updated the jwt-auth config to use this, and also updated the namespace in AuthServiceProvider. I assume it works fine at least one way as I'm able to generate a jwt token no problem.

I tried setting mine up just like your example above, but JWTAuth::parseToken()->authenticate() is always null.

Yeah check out my response in #384. You're using the stock Lumen setup, which creates an auth driver called api, which will be supported by RequestGuard (and the callback you pass to it in viaRequest). But the default jwt-auth setup has authenticate() calling Auth::onceUsingId(), which is not supported on RequestGuard, so you're going to get some sort of illegal method error.

There are a couple of solutions that I mention there:

  1. Don't use authenticate (or its alias toUser), and define a viaRequest callback that works some other way ($payload = JWTAuth::getPayload() and then manually find the user from the sub claim with something like App\Models\Whatever\User::find($payload['sub'])). If you go this route, you'll also have to find a workaround for attempt, if you use it.
  2. Define a custom jwt-auth auth provider that doesn't rely on Laravel's Guards. The byId will basically be what I wrote in (1). The byCredentials depends on your needs, but will likely have to be capable of both finding a user by arbitrary credential queries AND validating the credentials (i.e. password hashing). Check out EloquentUserProvider's implementation of those functions, for example.
  3. Ultimately I think jwt-auth is going to be implemented as an auth driver for 5.2 and beyond, so instead of using the AuthServiceProvider at all, you'll just change your auth config to use driver jwt. You're more than welcome to help implement that change. :wink:

Actually you might even be able to get by with changing the auth driver to session and then using the provided middleware as normal... jwt-auth doesn't actually require any of the session-y aspects of the driver, so I don't _think_ it would fail...?

Here's a package i created (Auth Driver) - https://github.com/irazasyed/jwt-auth-guard

For this package. Took me some time to play around with the code due to lot of various issues but at the end it was worth it.

Works like a charm.

P.S Obviously it would be good if the official package implements the Auth Driver.

@irazasyed That's awesome! Thanks a lot :)

@tdhsmith I ended up parsing the payload manually, and that worked just fine, thanks for the help :)

@syropian No problem, please leave your feedback/report any issues (if any) :)

@irazasyed I've added the JWTGuard class now, although I don't have the time to test it properly right now. Feel free to give a spin guys :smile:

And note that it's a first stab, so not _everything_ is in there just yet

Could you please give some guidance on how to use the JWTGuard. Im trying to get jwt 0.6-dev to work with lumen 5.2 to but run into the 'Call to undefined method Illuminate\AuthRequestGuard::once()'

I also have problem with the 'Class cache.store does not exist' in Tymon\JWTAuth\Providers\Storage\Illuminate (works if you use app('cache')).

Thank you for a great work.

@tymondesigns Looks good, I can see some errors just by looking at it though. Will try it out soon, Thanks :)

@jake142 You're having the same problems that i had reported before #384. There's some issue.
And the once undefined issue that you're seeing is because that method doesn't exist. @tymondesigns should probably implement some of those common methods, Like I've done in my package. I can send PR if that's ok.

It might be easier to cut out the middleman and have JWTGuard implement Tymon\JWTAuth\Contracts\Providers\Auth directly? It doesn't really make a ton of sense for a stateless guard to implement once anyway, except to fulfill the requirements imposed by that intermediate provider.

At the very least there should probably be an auth provider specially for JWTGuard. That could be fixed right in the initial binding/setup.

@irazasyed Ah, ok, I thought that JWTGuard fixed the errors. Is the purpose of the JWTGuard to "replace" the default "guard"? If so, how do I configure my project to use the JWTGuard instead of the default "guard" (sorry about the newbie questions)

@jake142 Yes you are correct. The guard forms the basis of what becomes the Auth facade, and the center of the overall auth system. There are issues with Lumen because it has a very different default driver than Laravel does.

You would change it by setting in config/auth.php:

'driver' => 'jwt'

In 5.2+, there will be an entry like that in the guards array for every corresponding label in the defaults array (though on stock Lumen there is only one default, called 'api').

Beware though, @tymondesigns just added this yesterday and it's mostly untested. There won't be much help available for problems you run into.

Hi @tdhsmith
I have this in my config/auth.php:

<?php

return [
    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
    ],
];

And this is the error message I have when accessing an endpoint that requires authorization:

InvalidArgumentException in AuthManager.php line 71:
Auth guard [] is not defined.

Am I missing something in my config/auth.php file?

I added this so it knows that we the default guard is the api guard defined:

    'defaults' => [
        'guard' => 'api',
        'provider' => 'users'
    ],

But then in my case it fails at trying to instantiate AwtGuard from within JwtAuth, which causes a circular call, eventually crashing.

@naerymdan thanks!
After adding the defaults I'm now facing the same issue as you: the circular reference is crashing my local Nginx. :-(

@jfoliveira yeah I've been trying to migrate a laravel+angular tutorial to Lumen, but a lot of dependencies have only partial support for Lumen, especially without Facades (why would anyone re-enable Facade, or worse, Eloquent on Lumen? Kinda defeats the purpose of using it in the first place).

https://github.com/naerymdan/lumen5-angular-material-starter <- if you are curious (or want to help out when you figure it out :D)

@irazasyed @tymondesigns any idea on why/where is the circular reference happening leading the application to crash?
I tried debugging the package for a few hours but it's still not clear to me what's the root cause of the issue.

@jfoliveira I've just pushed an update which might solve the issue you mention. I wasn't happy with the implementation so far, so hopefully this will kill 2 birds with 1 stone. Let me know if the issue persists

@tymondesigns The newest code did resolve the circular reference on boot, but I'm getting it again when I try to use the JWTAuth instance in my controller:

class LoginController extends Controller
{
    protected $auth;

    public function __construct(JWTAuth $auth)
    {
        $this->auth = $auth;
    }

...
}

Mostly, it seems to circle around in the protected function extendAuthGuard() in LumenServiceProvider.php, when it tries to create the JwtGuard

@tymondesigns I was able to move forward by registering the JWT base class itself to use in the JwtGuard instantiation.

        $this->app->alias('tymon.jwt.auth.jwt', \Tymon\JWTAuth\JWT::class);

@tymondesigns thanks for pushing this new update!

I've just updated and I am seeing this Fatal Error:

Fatal error: 
Cannot use Tymon\JWTAuth\Contracts\Providers\JWT as JWT because the name is already in use in
/vendor/tymon/jwt-auth/src/Manager.php on line 17

Changing Manager.php on line 17:

// use Tymon\JWTAuth\Contracts\Providers\JWT;
use \Tymon\JWTAuth\JWT;

Leads to:

ErrorException in Manager.php line 50:
Argument 1 passed to Tymon\JWTAuth\Manager::__construct() must be an instance of 
Tymon\JWTAuth\JWT, 
instance of Tymon\JWTAuth\Providers\JWT\Namshi given, 
called in 
/vendor/tymon/jwt-auth/src/Providers/LumenServiceProvider.php on line 141

@naerymdan @jfoliveira If you are using the Auth Guard feature in 5.2 then you don't need to use the JWTAuth facade at all. It's one or the other basically.

So just use the methods defined on the Guard instance and you should be golden. e.g.

$token = Auth::attempt($credentials); // get a token

Auth::user(); // get the authenticated user
Auth::setToken('foo.bar.baz')->user();

Auth::logout(); // invalidate the token and unset authenticated user
$refreshedToken = Auth::refresh(); // refresh the token parsed from the request

// etc...

@tymondesigns thanks for commenting. I must be doing something really stupid then, as no matter what I do I'm getting PHP error:

Fatal error: Maximum function nesting level of '256' reached, aborting!
in /vendor/illuminate/container/Container.php on line 698

The route I'm using for testing is exactly that, with hardcoded credentials:

$app->get('/auth', function (Request $request) {
    $credentials = ['email' => '[email protected]', 'password' => 'valid-password'];
    $token = Auth::attempt($credentials);
    dd($token);
});

In the routes file I'm just using use Illuminate\Http\Request. Should I reference JWTAuth as Auth?

My config/auth.php file:

<?php

return [
    'defaults' => [
        'guard' => env('AUTH_GUARD', 'api'),
        'provider' => 'users',
    ],
    'guards' => [
        'api' => [
            'driver' => 'jwt',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
    ],
];

My bootstrap/app.php file:

$app->routeMiddleware([
    // 'auth' => App\Http\Middleware\Authenticate::class,
    'jwt.auth' => Tymon\JWTAuth\Middleware\Authenticate::class,
    'jwt.refresh' => Tymon\JWTAuth\Middleware\RefreshToken::class,
]);
...
$app->register(App\Providers\AppServiceProvider::class);
// $app->register(App\Providers\AuthServiceProvider::class);
$app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);

As @naerymdan mentioned the LumenServiceProvider->extendAuthGuard is not able to resolve $app['tymon.jwt.auth'].
But for me adding an alias for it didn't change anything.

Any ideas? :-(

Whoops my bad. I forgot to update the service provider (it was late :smile:)

try running a composer update

also see #394 #368 #277

I'm getting 'Undefined index: provider' in LumenServiceProvider. This is my config/auth.php

<?php
    return [
        'defaults' => [
        'guard' => env('AUTH_GUARD', 'api'),
        'provider' => 'users',
    ],
    'guards' => [
        'api' => [
            'driver' => 'jwt',
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
    ],
];

@tymondesigns It works for me, but requires me to enable both Eloquent and Facades.

Without Facades I can't use the Auth::function() and simply injecting JWTGuard in my constructor fails at building it.

Without Eloquent (using 'database' driver), it fails because GenericUser does not implement JWTSubject.

Is there any way to make this work without either Facades and Eloquent?

@jake142 Add a provider for your guard:

    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users'
        ],
    ],

Ah, thanks. I just noticed that. Now I get:
Argument 1 passed to TymonJWTAuth\JWT::fromUser() must be an instance of TymonJWTAuth\Contracts\JWTSubject instance of App\Models\User given.

I use both facedes and eloquent. What am I doing wrong?

I personally added the interface and function to my User class, but there might be a better way:

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    JWTSubject
{
...
    public function getJWTIdentifier() {
        return $this->id;
    }

    public function getJWTCustomClaims() {
        return [];
    }
}

Thank you, got it working now

@tymondesigns working like a charm!
Thanks for your high quality support!

I ran into another problem. When I do Auth::getPayload() I get 'A token is required' (JWTAuth::getPayload() works).

@jake142 56f98aae78336e63bad3f2fa80279726a732f983 should have fixed that

@tymondesigns I've just updated and Auth::getPayload() is now working. Thank you very much!

Another issue that might be related: calling Auth::tokenById as in:

        if (!$token = Auth::tokenById($objUsuario->id)) {
            throw new UnauthorizedHttpException('Unauthorized.');
        }

Returns HTTP 400:

{
  "message": "Token could not be parsed from the request."
}

Is a token needed to retrieve the token based on existing userId?
I also tried Auth::fromUser but it doesn't seem to be available on Lumen's Auth fa莽ade, so I guessed tokenById was the way to go.

Without Facades I can't use the Auth::function() and simply injecting JWTGuard in my constructor fails at building it.

@naerymdan What's failing in the constructor? Could you just use app('auth') or am I misunderstanding Lumen?

@tdhsmith Nope, you got it right, I was originally trying to inject JWTGuard directly in my constructor, which failed, but I succeeded in doing the following:

    /**
     * @var \Illuminate\Contracts\Auth\Factory
     */
    protected $auth;

    /**
     * @param  \Illuminate\Contracts\Auth\Factory $auth
     */
    public function __construct(Auth $auth)
    {
        $this->auth = $auth;
    }

And now i simply tried app('auth')... and it works -_-' I feel kinda... yeah.

Haha no problem! I'm still not completely comfortable with the service container, so I frequently make silly mistakes with facades and singletons and injection and... :disappointed_relieved:

I'm not sure if this is an error or if Im doing something wrong. If I do the following:

  1. Login my user and receive a token
  2. Change the user id of that user (db seed for example)
  3. Try to access a resource with the middleware TymonJWTAuth\MiddlewareAuthenticate::class

The filter doesn't throw an error (it passes through the middleware). I guess this is since the token is valid. If I remember correctly an error was thrown in version 0.5 (invalid token I think)?

+10 for all the requests coming from #401 for Lumen 5.2 instructions.

If nothing else, getting everyone "up & running" the same way will help ensure new issues coming in are actual issues & not installation errors.

i am try to get token using only Email but i am get error this one -> Argument 1 passed to TymonJWTAuth\JWT::fromUser() must be an instance of TymonJWTAuth\Contracts\JWTSubject, instance of App\Model\User given, called in C:\wamp\www\users-project\vendor\illuminate\support\Facades\Facade.php on line 217 and defined

i am using this code to Get token ->
$user=User::where('email','=','[email protected]')->first();

if (!$userToken=JWTAuth::fromUser($user)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}

return response()->json(compact('userToken'));

Please help me how i get token using only rmail address

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mcblum picture mcblum  路  48Comments

SimonErich picture SimonErich  路  23Comments

punnawat picture punnawat  路  30Comments

kennethtomagan picture kennethtomagan  路  25Comments

chilio picture chilio  路  22Comments