Closed mtpultz closed 6 years ago
+1
This is already being worked on on the develop branch. (cf. JWTGuard.php)
If you're interested, there are some discussions on the matter at #376, #384, and #479, among others.
Thanks @tdhsmith, I'm very interested. Thanks for the links. Are there any areas that you need help on to make this usable for early stages of development using Laravel 5.2? From the links it appears that Laravel 5.2 has a lot of methods implemented, but I'm not familiar enough with it to tell how far along it is. Most of the issues are related to Lumen integration. Trying to time this with our needs of upcoming development.
@tdhsmith are there steps to setup the use of the JWTGuard to test it out? The current wiki setup doesn't seem to work the same way.
Are there any areas that you need help on to make this usable for early stages of development using Laravel 5.2?
Well I haven't personally worked on any of the new guard stuff, so I don't have a good sense myself. Perhaps @tymondesigns has suggestions. I think the main goals are just to test it thoroughly (are there unit tests for everything?) and then evaluate which other methods on SessionGuard
might be useful to adapt for it.
are there steps to setup the use of the JWTGuard to test it out?
Not yet. I think it should be sufficient to set your auth driver to jwt
(assuming you've already done the config stuff in the docs). Then in general you should replace calls to JWTAuth
with calls to Auth
itself, and explore the new helpers this provides. In particular you should test out "traditional" auth sugars like Auth::user()
, Auth::guest()
, Auth::id()
, and Auth::logout()
. (If you're working in an app that already accomplishes everything with middleware route gating though, you probably won't see big changes then, since you probably aren't running any checks like this anywhere.)
These are some other thoughts I've had on ways forward. They might not be exactly related to your question, but I figured I would share them here as long as I have them:
JWTGuard --> JWT --> auth provider --> JWTGuard --> user provider
. In any case, I think it's probably dangerous to keep using the same provider class, Illuminate
, for both guards. The guards inherently behave different and we shouldn't limit ourselves by expecting them to both conform to one provider. (I've argued previously that I don't think we should keep the once
method names in JWTGuard
because they imply an alternative that doesn't exist in a stateless auth system, but that might only be opinionated bikeshedding on my behalf.)@tdhsmith on your first point, I removed the JWTAuth
dependency from the JWTGuard
so there is no weird auth call stack now, only JWTGuard --> JWT --> Laravel's AuthManger
. Or did I miss something?
And you second point, It has been on my mind for a while, that it would be great to have concrete examples where people can see how things plumb together. Once I get this next release out of the way, that will be next I think.
Nope you're totally right. I was simultaneously looking at pre-guard code and the new code, and not keeping them separate in my mind. The JWT / JWTAuth split keeps that cycle stuff from happening. Smart choice! :+1:
This is the L5.1 demo app that led me here: https://laracasts.com/discuss/channels/laravel/starter-application-vuejs-laravel-dingo-jwt
This is just a post of the steps to get the JWTGuard in place for an API using Laravel 5.2.x to possibly help with starting the documentation of this feature, but also to see if this is the best way to implement JWTGuard. For example, is there a way that not so many methods need to be overridden with one or two changes.
1) config/app.php
- add Tymon\JWTAuth\Providers\LaravelServiceProvider::class
to providers
2) In your terminal publish the config file: php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
3) In your terminal generate the secret: php artisan jwt:secret
4) config/auth.php
- set the default guard to api
, and change the api
driver to jwt
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
...
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
5) /app/routes.php
- add a few basic authentication routes
Route::group([
'prefix' => 'api'
], function () {
$this->post('login', 'Auth\AuthController@login');
$this->get('logout', 'Auth\AuthController@logout');
Route::group([
'prefix' => 'restricted',
'middleware' => 'auth:api',
], function () {
Route::get('/test', function () {
return 'authenticated';
});
Route::get('/index', 'HomeController@index');
});
});
6) /app/AuthController.php
Authentication appears to almost work out of the box using the JWTGuard. To maintain the existing throttling I copied the AuthenticateUsers::login
into AuthController
and edited the second parameter of the call to attempt
from $request->has('remember');
to be the default used in JWTGuard::attempt
, and stored the $token for use.
public function login(Request $request)
{
...
if ($token = Auth::guard($this->getGuard())->attempt($credentials)) {
return $this->handleUserWasAuthenticated($request, $throttles, $token);
}
...
}
7) AuthenticatedUsers::handleUserWasAuthenticated
also needs to know about the $token
protected function handleUserWasAuthenticated(Request $request, $throttles, $token)
{
if ($throttles) {
$this->clearLoginAttempts($request);
}
if (method_exists($this, 'authenticated')) {
return $this->authenticated($request, Auth::guard($this->getGuard())->user(), $token);
}
return redirect()->intended($this->redirectPath());
}
8) AuthenticateUsers::handleUserWasAuthenticated
checks for a method authenticated
, which I added to AuthController
to respond when authentication is successful
protected function authenticated($request, $user, $token)
{
return response()->json([
'user' => $user,
'request' => $request->all(),
'token' => $token
]);
}
9) AuthenticatesUsers::sendFailedLoginResponse
can be pulled up to AuthController
to respond with JSON instead of redirecting failed authentication attempts
protected function sendFailedLoginResponse(Request $request)
{
return response()->json([
'message' => $this->getFailedLoginMessage(),
'username' => $this->loginUsername(),
'request' => $request,
]);
}
10) User
model needs to implement Tymon\JWTAuth\Contracts\JWTSubject
(see #260, and JWTSubject)
...
use Tymon\JWTAuth\Contracts\JWTSubject as AuthenticatableUserContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract,
AuthenticatableUserContract
{
use Authenticatable, Authorizable, CanResetPassword;
...
/**
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey(); // Eloquent model method
}
/**
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}
11) Test out whether authentication works by hitting the /login
route with Postman by setting the header to key: Authorization
, value: Bearer TOKEN_STRING
12) In order to hit the authenticated routes /test
this setup will work, but if you're using the HomeController and hitting a route like /index
, make sure you remove the auth
middleware from the controller's constructor, otherwise the routes to actions within the HomeController won't work.
public function register(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
$this->create($request->all());
$credentials = [
'username' => $request['username'],
'password' => $request['password'],
];
$token = Auth::guard($this->getGuard())->attempt($credentials);
return response()->json(['token' => $token]);
}
To logout invoke JWTGuard's logout method, which invalidates the token, resets the user, and unsets the token.
public function logout()
{
Auth::guard($this->getGuard())->logout();
// ...
}
Thanks @mtpultz, would someone be able to post a similar guide to using JWTGuard with Lumen as well?
@tymondesigns seems worth adding a login action to the guard to allow for easy registration (this is more of a note). If I have time on the weekend I'll have a look at it.
How is this coming along? I have it working but Im forced to use dev-develop
so that I can use the JWTGuard login method.
+1
+1, is that included into next first stable release (1.0)?
Can someone explain me what is this parameter doing / what is going on in this line ?
public function attempt(array $credentials = [], $login = true)
{
// ...
return $login ? $this->login($user) : true;
// ...
}
@3amprogrammer by default the method will return a token, but if you pass false as the second param then a boolean will be returned, indicating whether the credentials are valid
@tymondesigns thanks for explanation. It is kinda strange cause when this method returns either a token or a boolean true we can be sure that the credentials where valid.
Can you show logout function in auth controller? @mtpultz
@mtpultz thats really great .. also need logout example :+1:
Hi @HlaingTinHtun and @ghprod,
/**
* Log the user out of the application.
*
* @return \Illuminate\Http\Response
*/
public function logout()
{
Auth::guard($this->getGuard())->logout();
// ...
}
This invokes JWTGuard's logout method, which invalidates the token, resets the user, and unsets the token.
/**
* Logout the user, thus invalidating the token.
*
* @param bool $forceForever
*
* @return void
*/
public function logout($forceForever = false)
{
$this->requireToken()->invalidate($forceForever);
$this->user = null;
$this->jwt->unsetToken();
}
@mtpultz Thanks. It works successfully
To keep the complete answer all in one place I've also added the logout to the original post of the "guide", also added a few changes with regards to the artisan commands.
@mtpultz I'm geting Method [handle] does not exist.
with Lumen (using dev-develop) after following your instructions on how to configure auth.php.
This was the result of trying to apply the Middleware to my routes group (using Dingo/Api).
I guess that this happens because it's unable to find the middleware, but I really have no idea :sweat_smile:
Any clue why this happens?
Thanks for reading :muscle:
Edit: Here's a fragment of the trace returned with the error
"#0 [internal function]: Tymon\\JWTAuth\\JWTGuard->__call('handle', Array)",
"#1 /home/vagrant/Code/sistema-trabajos/vendor/illuminate/auth/AuthManager.php(288): call_user_func_array(Array, Array)",
"#2 [internal function]: Illuminate\\Auth\\AuthManager->__call('handle', Array)",
"#3 /home/vagrant/Code/sistema-trabajos/vendor/illuminate/pipeline/Pipeline.php(136): call_user_func_array(Array, Array)",
"#4 [internal function]: Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Dingo\\Api\\Http\\Request))",
Never mind, forgot to register the middleware, which is commented-out by default in Lumen. My bad :sweat:
@mtpultz for that should i install the dev-master? using composer? thanks
Hi @antonioreyna, yah you'll need dev-master. That's the branch the currently has the JWTGuard class. If you look in /src
of the master branch you'll see that there is no JWTGuard.php file, but switching to dev-master you'll see it.
Cheers
I'm sorry for this noobish question but i don't get it after step 6. where can i find AuthenticateUsers::login
?
Hi @pouyaamreji,
The AuthenticatesUsers::login is a trait applied to the AuthController. It is actually a composite trait since it is a trait within the AuthenticatesAndRegistersUsers trait that you see being used by the AuthController. If you need to find these files, and your IDE doesn't provide a shortcut to indexed classes or methods, look at the use statement at the top of the file:
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
This is the mapping to the file in your composer vendors file so you'd look inside:
vendors/larave/framework/src/Illuminate/Foundation/Auth
In there you'll find the AuthenticatesAndRegistersUsers class and if you open it up you'll see the AuthenticatesUsers class being used, and you'll also see the AuthenticatesUsers file in the same folder. Inside that is the login action you're trying to find.
Hi. I am working on getting the guard functionality working. If I install dev-master through Composer, the JWTGuard.php file is not included in source. If I install dev-develop, however, JWTGuard is included, but I get an error: Fatal error: Class 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider' not found when I include it in app.php under service providers. Any suggestions would be most appreciated.
@adstechnetwork if you take a look at the providers in the src directory, you'll see there is a LumenServiceProvider
and a LaravelServiceProvider
now.
Thank you @feeekkk . That worked. However now, when I make a post to login I get the following reflection exception: Class Tymon\JWTAuth\Providers\JWT\NamshiAdapter does not exist.
Any thoughts?
@adstechnetwork one of these should help you https://github.com/tymondesigns/jwt-auth/search?q=NamshiAdapter&type=Issues&utf8=%E2%9C%93
@feeekkk thank you very much. That fixed that piece and I can keep moving forward. Most appreciated.
Hi, ( EDITED: see on bottom) I don't know what I am doing wrong, but I am handling register,login,logout in User Controller. I am using Laravel 5.3.
I have two issues:
First issue I did copy AuthenticateUsers::login to UserController and changed it to
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->credentials($request);
if ($token = Auth::guard($this->getGuard())->attempt($credentials)) {
return $this->handleUserWasAuthenticated($request, $throttles, $token);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
if (! $lockedOut) {
$this->incrementLoginAttempts($request);
}
return $this->sendFailedLoginResponse($request);
}
$this->validateLogin
and all others of course does not exist. What should I do?
Second issue In User model I have following code:
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Validator;
use Tymon\JWTAuth\Contracts\JWTSubject as AuthenticatableUserContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract,
AuthenticatableUserContract
{
use Notifiable, Authenticatable, Authorizable, CanResetPassword;
Authenticable is giving errors already in IDE.
All implements: Class must be declared abstract or implement methods sendPasswordResetNotification, getEmailForPasswordReset
and Authenticatable
- Trait expected, class found
Can someone help?
EDIT:
I realized that I should use AuthenticatesUsers
trait.
I did add it, but now I am getting error: Method [getGuard] does not exist.
in this line
if ($token = Auth::guard($this->getGuard())->attempt($credentials)) {
and now I really have no idea what should I do ?
`
Hi @mariaczi, so this post is really for Laravel 5.2 since 5.3 was only released 3 days ago, but that said it should probably be updated for 5.3 so if I have time I might drop something in this weekend since I have a new project that needs to be started up. That said the gist is the same you have a token and you need to get it to the client so hopefully these points might help:
Hope this helps
@mtpultz I and probably others would be really grateful if you would find time for complete tutorial for 5.3 to let understand it correctly as 5.3 changed some main ideas behind.
EDIT:
I moved login function to LoginController but again after fixing few errors - I got the same with Method [getGuard] does not exist.
Can you advise ?
@mtpultz Could you please show me your middleware file, you defined 'middleware' => 'auth:api', I don't know how to use the middleware . I write it like this:
<?php
namespace App\Http\Middleware;
use Closure;
use Tymon\JWTAuth\Facades\JWTAuth;
use Exception;
class authJWT
{
public function handle($request, Closure $next)
{
try {
$user = JWTAuth::toUser($request->input('token'));
} catch (Exception $e) {
if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException){
return response()->json(['error'=>'Token is Invalid']);
}else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException){
return response()->json(['error'=>'Token is Expired']);
}else{
return response()->json(['error'=>'Something is wrong']);
}
}
return $next($request);
}
}
But it didn't work. It couldn't return the exception for json. Hope you can help me .Thanks.Happy Mid-Autumn Festival
Hi @kylesean, I actually don't use JWTAuth I use the JWTGuard in Laravel 5.2+ so I don't have to write any middleware like you are doing.
Hi, I have written a lesson on getting this package working with Lumen 5.3 on my website JSON Web Token Authentication for Lumen REBOOT
I've added an updated version for getting JWTGuard working with Laravel 5.3.x #860.
I have such an error with laravel 5.2 BadMethodCallException in JWTGuard.php line 405: Method [handle] does not exist.
You might have to provide a bit more information like the version of JWT Auth that you're using, maybe the action that is calling JWTGuard, etc
Ran into this issue as I have 2 users tables and needed the authentication to work on both, so I found this Setting the Guard Per Route in Laravel simple customization to use the "guard" in the routes.
Leaving it here as it's working fine for me and might work for others.
The docs indicate it is possible to create your own implementation of Illuminate\Contracts\Auth\Guard and registering it as a driver in a service provider.
I was reading about the new stateless token authentication that was added in 5.2 in a JacobBennett Gist (the docs are really vague), but it doesn't appear to be the same as JWT tokens. That said it would be amazing to be able to leverage Laravel's API the same way.
Would it be possible to create a custom driver to reduce the amount of changes required to implement JWT tokens, and reduce the API a bit so using more of Laravel's API? For example getting a user is
Auth::guard('api')->user();
using the API guard, and the equivalent could beAuth::guard('jwt')->user();