tymondesigns / jwt-auth

🔐 JSON Web Token Authentication for Laravel & Lumen
https://jwt-auth.com
MIT License
11.26k stars 1.55k forks source link

Using JWTGuard with Laravel 5.3.x "Guide" #860

Open mtpultz opened 7 years ago

mtpultz commented 7 years ago

Okay, I've got a bit of time, and need to set up a new project with Laravel 5.3 and JWTGuard. @tymondesigns I hope you don't mind this being in your issues similar to my other "guide" (#513), and if so I can try and find a different spot, but I don't write blogs or tutorials.

This is just a post of the steps to get the JWTGuard in place for an API using Laravel 5.3.x to possibly help with starting the documentation of this feature. Anything from #513 that is the same is still included so you don't need to browse through it unless I missed something. I've set this up twice now, and I found myself that it is just easier to do a clean install, and port. So this assumes you are doing a clean install, and currently are using version v5.3.11 of Laravel .

Also, jwt-auth 1.0 hasn't been released yet so this also assumes that you are pulling in the development branch "tymon/jwt-auth": "dev-develop", using composer.

Login Using JWTGuard with Laravel 5.3.x

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", and add it to your list service providers in app.php 3) In your terminal generate the secret: php artisan jwt:secret provided you have JWT_SECRET in your .env file 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) /routes/api.php - add a few basic authentication routes

Route::post('login', 'Auth\LoginController@login');

Route::group([
    'prefix' => 'restricted',
    'middleware' => 'auth:api',
], function () {

    // Authentication Routes...
    Route::get('logout', 'Auth\LoginController@logout');

    Route::get('/test', function () {
        return 'authenticated';
    });
});

6) /app/http/Controller/auth/LoginController.php

Authentication appears to almost work out of the box using the JWTGuard. To maintain the existing throttling I copied the AuthenticateUsers::login into LoginController and removed the second parameter of the call to attempt from $request->has('remember'); to let it use the default in JWTGuard::attempt, and stored the $token for use, and passed it along to LoginController::sendLoginResponse

public function login(Request $request)
{
    ...

    if ($token = $this->guard()->attempt($credentials)) {
        return $this->sendLoginResponse($request, $token);
    }

    ...
}

7) AuthenticatedUsers::sendLoginResponse needs to know about the $token so I pulled it up to the LoginController and removed $request->session()->regenerate(); since JWT is stateless.

protected function sendLoginResponse(Request $request, $throttles, string $token)
{
    $this->clearLoginAttempts($request);

    return $this->authenticated($request, $this->guard()->user(), $token);
}

8) AuthenticateUsers::sendLoginResponse checks for a method authenticated, which I added to the LoginController to respond when authentication is successful

protected function authenticated(Request $request, $user, string $token)
{
    return response()->json([
        'token' => $token,
    ]);
}

9) AuthenticatesUsers::sendFailedLoginResponse can be pulled up to LoginController to respond with JSON instead of redirecting failed authentication attempts

protected function sendFailedLoginResponse(Request $request)
{
    return response()->json([
        'message' => Lang::get('auth.failed'),
    ], 401);
}

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, Notifiable;

    ...

    /**
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();  // Eloquent model method
    }

    /**
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [
             'user' => [ 
                'id' => $this->id,
                ... 
             ]
        ];
    }
}

11) Test out whether authentication works by hitting the /login route with Postman using a set of valid credentials, which should return a token.

12) In order to test this setup hit the authenticated route /test without setting the token in the header, which should respond with an error message, and then hit /test again after setting the header to key: Authorization, value: Bearer TOKEN_STRING, which should respond with the string authenticated.

Registration Using JWTGuard with Laravel 5.3.x

  1. The RegistersUsers trait applied to the RegisterController contains RegistersUsers::register, which needs to be pulled up to the RegisterController and updated to return a response instead of a redirect.
public function register(Request $request)
{
    ...

    return response()->json([
        'message' => trans(...),
    ]);
}

Logout Using JWTGuard with Laravel 5.3.x

To logout invoke JWTGuard's logout method, which invalidates the token, blacklists the token (assuming you kept the config defaults), resets the user, and unsets the token.

public function logout()
{
   $this->guard()->logout(); // pass true to blacklist forever

    // ...
}
mtpultz commented 7 years ago

@anjosi try /api/restricted/test. You have a prefix of api in your route service provider, and restricted in your api.php routes

anjosi commented 7 years ago

/api/restricted/test seems to be correct. However it only works if I comment out the middleware element in the argument list. Should I register the auth:api in kernel.php? To which middleware class that alias refers?

anjosi commented 7 years ago

I've finally got thing working by setting up the laravel passport as instructed in this wonderful video (https://laracasts.com/series/whats-new-in-laravel-5-3/episodes/13) by Taylor Otwell.

hazzo commented 7 years ago

After struggling a bit I got it running on Laravel 5.3. If I can help some one let it me know. Files ended being kind of different in some occasions. I will try to post them later.

maikdiepenbroek commented 7 years ago

@hazzo Been struggling a bit myself but got it to work, please post your results so i can compare.

hazzo commented 7 years ago

@maikdiepenbroek Yes here are de LoginController, api routes and User model.

Here I used the same methods only that variable $credentials did not have any value, so I look inside the Laravel login trait and used the same method to crate credentials that Auth uses $credentials = $this->credentials($request);. Also the string part of Auth gave me error when inserting it in the second parameter with the $token. So I eliminate it, I rally don't know how string work but I did not find it necessary because the token I will always be passing is a string...

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Lang;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    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 ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        $credentials = $this->credentials($request);

        if ($token = $this->guard()->attempt($credentials)) {
            return $this->sendLoginResponse($request, $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.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Log the user out of the application.
     *
     * @param \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(Request $request)
    {
        $this->guard()->logout();

    }

    /**
     * Send the response after the user was authenticated.
     *
     * @param Request|\Illuminate\Http\Request $request
     * @param string $token
     * @return \Illuminate\Http\Response
     */
    protected function sendLoginResponse(Request $request, $token)
    {
        $this->clearLoginAttempts($request);

        return $this->authenticated($request, $this->guard()->user(), $token);
    }

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  mixed $user
     * @param string $token
     * @return mixed
     */
    protected function authenticated(Request $request, $user, $token)
    {
        return response()->json([
            'token' => $token,
        ]);
    }

    /**
     * Get the failed login response instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse
     */
    protected function sendFailedLoginResponse(Request $request)
    {
        return response()->json([
            'message' => Lang::get('auth.failed'),
        ], 401);
    }

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest', ['except' => 'logout']);
    }
}

The User model was a bit difficult. By any means it will work with the above settings. I have to look for the elements that constitute the Authenticable Class and use them in the model. I also eliminate the classes that were not been used.

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Tymon\JWTAuth\Contracts\JWTSubject as AuthenticatableUserContract;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract,
    AuthenticatableUserContract
{
    use Authenticatable, Authorizable, CanResetPassword;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();  // Eloquent model method
    }

    /**
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [
            'user' => [
                'id' => $this->id,
             ]
        ];
    }
}

For me the routes where the same. I did only eliminate the prefix. Did not need it. I have to say that i did all this with a CLEAN Laravel install.

With this settings I recibe a token I can logout with above settings too. Once this is clear it's quite faster than OAuth.


<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

// Login
Route::post('login', 'Auth\LoginController@login');

Route::group([
    'middleware' => 'auth:api',
], function () {

    // Authentication Routes...
    Route::get('logout', 'Auth\LoginController@logout');

    Route::get('test', function () {
        return 'authenticated';
    });

});

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:api');
maikdiepenbroek commented 7 years ago

@hazzo Thnx for the code example, it's more or less the same as i have so thats a good confirmation.

The string is called Scalar type hint, more info here: https://wiki.php.net/rfc/scalar_type_hints

hazzo commented 7 years ago

@maikdiepenbroek oh thanks, I supposed that it was something like that (used int many times) but I don't know why it was not working.

JesusGR4 commented 7 years ago

Hello @hazzo , when I use 'auth:api' in middleware it gives me an error, any idea? PD: Show me your Kernel.php, jwt.php and auth.php pls :(

aijcoa commented 7 years ago

Command "jwt:secret" is not defined.

jwt:generate doesn't seem to set the key anywhere either.

Version 0.5.9

hazzo commented 7 years ago

@JesusGR4 what kind of error do you get?

Here is 'auth.php', Kernel and JWT are untouched.


<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

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

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

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

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

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

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

Maybe you are missing this aliases in your app.php


        'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
        'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class
benkingcode commented 7 years ago

Why is this still not documented?

sourcesita commented 7 years ago

@hazzo I love u man, that code example help me a lot, was stuck for 2 hours. +1

LTFENG commented 7 years ago

Hi, I would like to use jwt-auth 1.0.x in laravel 5.4 for JWT multiple table Is it possible ?

ebarault commented 7 years ago

@mtpultz many thanks for bootstrapping me into this. I had just finished a skeleton with vanilla jwt-auth when i saw your post. The move to jwt-guard is worth it 👍 I'd highly recommend to repost this how-to in a different issue so to clean up a bit the mess with comments.

For anyone wondering how to get rid of the override the default auth config in config/auth.php

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

just override the auth provider for guard in your LoginController :

  /**
   * Get the guard to be used during authentication.
   * Overrides default Laravel method to get the 'api' guard
   *
   * @return \Illuminate\Contracts\Auth\StatefulGuard
   */
  protected function guard()
  {
      return Auth::guard('api');
  }

the important change is the 'api' in return Auth::guard('api');, this will make sure you do not fallback to default auth provider when calling method like $token = $this->guard()->attempt($credentials)

this way you can use any number of auth provider in you app than needed, by specifying in related controllers which provider to use each time

jeff-h commented 7 years ago

This should be in the wiki. That would be really helpful.

Secondly, any chance anyone has some code to share for 5.4? I wish there was a repo somewhere with the basics covered, like this tutorial does.

mediaceh commented 7 years ago

It sample not working absolutely. I spent more five hours to get it work.

cvgeldern commented 7 years ago

I get {token: "1"} as response if I login successfully, instead of a token. I can not trace the reason of this behaviour. I'm using Laravel 5.4 and the dev-develop branch (1.0.x) in composer.json. With some minor 5.3->5.4 tweaks researching errors on the go, this seems to be the last problem I can't tackle myself to get this fully functional.

ebarault commented 7 years ago

Shortest way : Change the default auth to "api" in auth.php

cvgeldern commented 7 years ago

I'm ashamed, how did I miss that one... I did have it set to api, but somehow I/something reverted it back. Probably staring too much at the code last few days... Thanks a lot!

HristoAntov commented 7 years ago

I made it all work (returning token upon successful login, restricting access to protected routes) in Laravel 5.3, but when I make a request to а protected route without a token, or with invalid token, I receive 404 Not Found, instead of 401 Unauthorized. Anyone facing this issue? Thanks in advance.

newkillerbeast2017 commented 7 years ago

@mtpultz I had a fresh Lumen 5.4 installation and followed this tutorial. Login and others work fine but the logout doesn't seem to work properly. What I mean is, if I try to expire a token it doesn't give me an error but if the same token is re-used, it should say expired but still goes through. In simple terms, I believe it is not expiring the token at all.

Here is my code:

class UserController extends Controller
{
    protected $jwt;
    public function __construct(JWTAuth $jwt)
    {
            $this->jwt = $jwt;
    }

    public function Signin(Request $request)
    {
        $this->validate($request, [
            'email'    => 'required|email|max:100',
            'password' => 'required|min:6',
        ]);

        if (!$token = $this->jwt->attempt($request->only('email', 'password'))) {
            return response()->json(['The credentials provided are invalid.'], 500);
        }

        return response()->json(compact('token'));
    }

    public function LogoutUser(Request $request){
        $this->jwt->invalidate($this->jwt->getToken());

        return response()->json([
            'message' => 'User logged off successfully!'
        ], 200);
    }
}

my routes:

$app->group(['prefix' => 'api'], function($app){
    $app->post('/signup', [
        'uses' => 'UserController@Signup'
    ]);

    $app->group(['middleware' => 'auth:api'], function($app){
        $app->post('/logout',[
            'uses' => 'UserController@LogoutUser'
    ]);
    });
});

my config/auth.php says:

'defaults' => [
    'guard' => env('AUTH_GUARD', 'api'),
],
'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users'
    ],
],
'providers' => [
    'users' => [
       'driver' => 'eloquent',
        'model'  => \App\User::class,
    ],
],
'passwords' => [
    //
],

Any help will be greatly appreciated.

johanchouquet commented 7 years ago

Hi, step 2 is telling me "class auth does not exist". Did someone already encountered that ? I pulled in : "tymon/jwt-auth": "dev-develop"

sukhpreetmcc commented 7 years ago

Hi, I am using 2 different guards to authenticate user i.e one for the customer and one for the admin 'customer' => [ 'driver' => 'jwt', 'provider' => 'customer', ], 'admin' => [ 'driver' => 'jwt', 'provider' => 'admin', ], I have two different tables and Models. I implemented the JWTGuard as mentioned in this post and I am able to login on different guard and it return the token as well. But, the problem I am facing is, with the returned token, both customer and admin can access each other's protected(pages which only admin and only customer can access) area. I mean, customer can access the admin area using the customer token. Actually, JWT uses id as a parameter to find the user from the token and if the user with the same id(customer id) exists in the admin it returns that user.

Please suggest me a solution.

kofi1995 commented 7 years ago

@sukhpreetmcc How did you get the jwt multi-auth guard to work. I've implemented something similar but the only guard that works is the one with the User model. The other guard doesn't work, it throws an error.

sukhpreetmcc commented 7 years ago

@kofikwarteng I used Laravel Mutil Guard to login

auth.php code 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'customer' => [ 'driver' => 'jwt', 'provider' => 'customer', ], 'admin' => [ 'driver' => 'jwt', 'provider' => 'admin', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ],

    'customer' => [
        'driver' => 'eloquent',
        'model' => App\Models\Customer::class,
    ],
    'admin' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admin::class,
    ]
],

admin.php model code namespace App\Models; use Illuminate\Notifications\Notifiable; //use Illuminate\Auth\Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable; use Config; use Illuminate\Database\Eloquent\Model; use Tymon\JWTAuth\Contracts\JWTSubject; class Admin extends Authenticatable implements JWTSubject { protected $guard = "admin"; public function getJWTIdentifier() { return $this->getKey(); // Eloquent model method } public function getJWTCustomClaims() { return [ 'user' => [ 'id' => $this->id, ] ]; } } Same code is used for the customer model as well.

admin auth controller code namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Auth,Hash,Mail; //use Illuminate\Support\Facades\Request; use Illuminate\Http\Request; use Validator,JWTAuth; use App\Models\Admin; use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; class AdminLoginController extends Controller { use AuthenticatesUsers; protected $redirectTo = '/home'; protected $guard = 'admin'; public function login(Request $request) { $this->validateLogin($request); if ($this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } $this->validate($request, [ 'email' => 'required|email', 'password' => 'required', ]); $adminAuth = auth()->guard('admin'); $credentials = $request->only('email', 'password'); if ($token = $adminAuth->attempt($credentials)) { return $this->sendLoginResponse($request, $token); } $this->incrementLoginAttempts($request); return $this->sendFailedLoginResponse($request); } protected function sendLoginResponse(Request $request, string $token) { $this->clearLoginAttempts($request); return $this->authenticated($request, auth()->guard('admin')->user(), $token); } protected function authenticated(Request $request, $user, string $token) { return response()->json([ 'request' => $request, 'user' => $user, 'token' => $token, 'success'=> true, 'message' => 'Successfully logged in' ]); }
}

And for customer login I don't specify guard as it is the default as mentioned in the auth.php file. It authenticates user from 2 different tables. The problem I am getting is the admin token can be used to authenticate the customer and vice-versa, which is incorrect.

kofi1995 commented 7 years ago

@sukhpreetmcc I still faced the same problem but I solved it by adding this line to CreateUsersProvider.php on like 65. The problem was due to the fact that the index was called 'table' instead of 'model':

    /**
     * Create an instance of the Eloquent user provider.
     *
     * @param  array  $config
     * @return \Illuminate\Auth\EloquentUserProvider
     */
    protected function createEloquentProvider($config)
    {
        if(isset($config['table'])) {
            $config['model'] = $config['table'];
        }

        return new EloquentUserProvider($this->app['hash'], $config['model']);
    }

I then realized the problem you were facing. I use the token to access the other guard's resource and I was able to access the resource since they both had the same id. We need to figure out a solution to that problem.

sukhpreetmcc commented 7 years ago

@kofikwarteng That's great, your problem is solved. Right, we need to find the solution for the second problem. I am trying to solve this. Do post here if you find any solution before me. I also expect other more experienced developers to help as well.

codex-corp commented 7 years ago

Thanks, very helpful guide. the below works for me!

$token = $this->guard()->claims( [
                'permissions' => '',
                'user_menu' => ''
            ])->attempt($credentials);
sukhpreetmcc commented 7 years ago

@kofikwarteng Hi, did you find any way out?

AndyYuenOk commented 7 years ago

How did you get the jwt multi-auth guard to work. I've implemented something similar but the only guard that works is the one with the User model. The other guard doesn't work, it throws an error. 期待更好的解决方案

chrishessler commented 7 years ago

@mtpultz First of all thank you for this guide. It helped me setting up JWT with JWTGuard. However I can't seem to find any information on how to correctly implement refresh tokens.

Could you please guide me through implementation of refresh tokens?

zanjs commented 7 years ago

route

 Route::group(['middleware' => 'auth:api'], function () {
     Route::get('/me', 'UserController@me');
 });

controlle

public function me()
    {
        return Auth::guard('api')->user();
    }

run is error

(1/1) InvalidArgumentException
Route [login] not defined.
mtpultz commented 7 years ago

Hi @chrishessler the refresh tokens are applied using the refresh middleware, and making sure they are turned on in your config. I haven't used them in a bit, but I think that is it. Let me know if that doesn't work and I can have a look at a previous project.

@zanjs not sure what your issue might be, but I'd check if you have a route named login by using artisan to display a list of your routes. If you don't have a login route specifically named login likely Laravel requires it, but from what you've posted there's no correlation between your route and the error.

sanderlooijenga commented 7 years ago

@zanjs this probably means you're not logged and therefor tries to send you to a login route (which doesn't exist (yet)). This happens in the App\Exceptions\Handler class in its unauthenticated method.

If the request to /me would have the following header; Accept: application/json then it would have different output (a 401 response with an error indicating you're unauthenticated).

zanjs commented 7 years ago

@sanderlooijenga Thinks !

francisrod01 commented 7 years ago

It is not possible to inform which guard I'd like to use to retrieve user information. https://github.com/tymondesigns/jwt-auth/wiki/Authentication

This error is occurring because I do not use the default guard because I'm implementing each one for a different social network.

BadMethodCallException
Call to undefined method Illuminate\Database\Query\Builder::getAuthIdentifierName()

So, I replace this line in my Middleware:

$user = $this->auth->authenticate($token);

to:

$user = $this->auth->getClaim('<inform the guard dinamically>');

and then:

array:7 [
  "id" => 9
  "firstname" => "3f207KW2ch1UyQvC"
  "lastname" => "LnaR3CDziAC9sfRw"
  "email" => "hk0BVs7aQDaRxeyn@myemail.com"
  "createdAt" => "2017-07-31 19:42:00-03"
  "lastUpdatedAt" => "2017-07-31 19:42:00-03"
  "lastAccess" => "2017-07-31 19:42:00-03"
]

... but you lose all control of exceptions under the token.

awd22 commented 7 years ago

When I initiate 'route:list' at the console, I still see web being the primary guard being used. I've even changed all of the guards to 'jwt', but sessions are still being used. Is there a way to hack the framework to just work with tokens? There's not enough documentation on how to properly implement Tymon's jwt-auth framework. I've used this tutorial to get past the [api] not defined error, but sessions are still being stored.

mtpultz commented 7 years ago

@awd22 it is definitely possible. What version of Laravel are you using 5.3, 5.4, 5.5? Where are you putting your routes web.php, api.php, etc? Have you looked in the RouteProvider?

awd22 commented 7 years ago

Laravel 5.4, The routes are in the default web.php file. I haven't modified anything in the RouteServiceProvider file

mtpultz commented 7 years ago

I think if you look in the RouteServiceProvider you'll see very quickly why putting your routes in web.php is using the web middleware.

awd22 commented 6 years ago

yeah I saw that Route::middleware was set to web... So do you have any suggestions where I should insert ajax requests at? Newby at JavaScript & HTTP stuff

awd22 commented 6 years ago

when i change it to api.. the whole front end breaks and it can't even bring up the landing page without an error

mtpultz commented 6 years ago

The route service provider is mapping middleware to a file. You're putting your routes in web.php, which has web middleware automatically applied to it so your routes have it applied. So you can either remove the middleware from mapWebRoutes (not advised since it can be used for views or responses that require cookies), put them in mapApiRoutes (api.php), or make up your own mapping.

Leave the route in web.php that serves your views, and put all your HTTP requests in api.php.

awd22 commented 6 years ago

I'm trying to follow you... so do I change the Route::middleware line for mapWebRoutes()? Because as soon as I change it from web to api, the whole app breaks and I get an error. I have a template for ajax requests but not sure how to put them in Laravel

quantumhype commented 6 years ago

@awd22 - I think the easiest way is to leave the web middleware alone. When you want to make ajax requests, use the /api/ route. If you followed this guide correctly, the api route should be protected by the 'api' guard, which means it's using Tymon's JWT middleware.

So, make ajax requests to: yourdomain.com/api/testRoute. (Notice /api/ in the name of your URL)

Edit Actually the middleware isn't automatically used. You need to append that middleware in api.php to your specific routes.

awd22 commented 6 years ago

yeah I understand the prefix part of the routeserviceprovider file. I'm trying to have every user be authenticated through tokens, and not have any sessions being recorded. Is that even possible in Laravel?

mtpultz commented 6 years ago

So you're saying that using the api middleware provided through api routes in api.php and that is creating sessions? I don't understand how that is possible. The api middleware by default is a middleware group that just throttles and does bindings. To create a session you'd have to be using the LoginController and then not making any JWTAuth changes listed above in the guide. I think you should probably revisit the changes you've made to LoginController, or show some code examples.

Also, @awd22 isn't talking about the prefix he's saying you need to do add the auth middleware similar to step 5 in the guide where it adds auth:api, which says for auth use the JWT driver applied to the API guard (see step 4) instead of Laravel's authentication driver that uses sessions.

awd22 commented 6 years ago
namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\AuthenticatesUsers; use App\User; use Illuminate\Http\Request; use JWTAuth; use Illuminate\Support\Facades\Redirect; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Validation\ValidationServiceProvider; use DB; use Tymon\JWTAuth\Exceptions\JWTException; class LoginController extends Controller { /* -------------------------------------------------------------------------- Login Controller
This controller handles authenticating users for the application and
redirecting them to your home screen. The controller uses a trait
to conveniently provide its functionality to your applications.
*/
use AuthenticatesUsers;
/**
 * Where to redirect users after login.
 *
 * @var string
 */
protected $redirectTo = '/home';
/**
 * Create a new controller instance.
 *
 * @return void
 */
public function __construct()
{

}

protected function authenticate(Request $request)  {
// grab credentials from the request
$rules = array(
'userid' => array('required','string','min:6','max:10','regex:/^(?=.*[a-zA-z])(?=.*\d)(?=.*(_|[^\w])).+$/'),
'password' => array('required','string','min:8','max:12','regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*(_|[^\w])).+$/'));
$messages = array(
'userid.regex' => 'The User ID must be 6-10 characters: 1 letter(Upper or Lower), 1 number, 1 special character_!@#$^&*',
'password.regex' => 'The Password must be 8-12 characters: 1 upper case, 1 lowercase, 1 number, 1 special character_!@#$^&*');

$this->validate($request, $rules, $messages);

if (!$this) {
    return Redirect::back();
}
else {
    $input = $request->all();
    $check = DB::table('users')->select('confirmed','confirmation_code')->where('userid',$input['userid'])->get();
    if (!$check) {
        return Redirect::back();
            }
    else {
            if ($check['confirmed'] = 1) {

            $credentials = $request->only('userid', 'password');
            try {
                $token = JWTAuth::attempt($credentials);
            if (!$token) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
            if ($token = $this->guard()->attempt($credentials)) {
                return $this->sendLoginResponse($request, $token);
                //return $insert = response($token)->header('Authorization', 'Bearer ' . $token);
            }
            } 
            catch (JWTException $e) {
            return response()->json(['error' => 'could_not_create_token'], 500);
                            }

                //$token = JWTAuth::parseToken('bearer', 'HTTP_AUTHORIZATION')->getToken();
                //return dd($token);
                //return dd(getallheaders());
                //return redirect()->route('profile');

                //return response()->json(compact('token'));
            }
            else {
                abort(403, 'Email needs to be authenticated.');
            }       

            }

}   
}
protected function sendLoginResponse(Request $request, string $token){
    $this->clearLoginAttempts($request);
    return $this->authenticated($request, $this->guard()->user(), $token);
}

protected function authenticated(Request $request, $user, string $token) {
    return response()->json(['token'=>$token, ])->header('Authorization', 'Bearer ' . $token);
}

protected function sendFailedLoginResponse(Request $request) {
    return response()->json(['message' => Lang::get('auth.failed'),], 401);
}

}

awd22 commented 6 years ago

My validation methods work before and your methods work.. I just dont know how to get rid of the sessions.. I just want JWT auth for everything.