tymondesigns / jwt-auth

🔐 JSON Web Token Authentication for Laravel & Lumen
https://jwt-auth.com
MIT License
11.28k stars 1.54k 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

And you've changed /config/auth.php to use the jwt driver for the api guard, and api is your default guard? That is where the default Laravel authentication using sessions is removed and replaced with JWT. After that you can use the auth middleware and it will default to the api guard. If api is not set to be the default guard then you need to explicitly use auth:api middleware on your routes. Now if you're sending up the token it gets read automatically and you can use Auth::method() to access the JWT API and the user like Auth::guard()->getUser() or Auth::guard($this->guard)->getUser(). Where are you seeing a session?

awd22 commented 7 years ago
'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' => 'jwt',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
mtpultz commented 7 years ago

So where are you seeing the session? and maybe provide the routes your using to login and then access authenticated routes.

awd22 commented 7 years ago

when I open developer tools, there is a set-cookie in the response/request headers... so the session is being stored in cookies on the browser.. so that's why I know sessions are still being used

mtpultz commented 7 years ago

Have you tried removing the session middleware from the web middleware group in Kernel.php?

awd22 commented 7 years ago

when I comment out all of the lines with session under $middlewareGroups, the whole app breaks & error comes up when trying to display landing page

cabromiley commented 7 years ago

If like me you run into this issue but setting the default guard to api is not an option then i suggest following this guide: http://mattallan.org/2016/setting-the-guard-per-route-in-laravel/ to set your driver. This fixed it for me.

matriphe commented 6 years ago

Worked like charm in Laravel 5.5 too.

tradzero commented 6 years ago

if i have two guard merchant and user, both use jwt driver, what can i distinguish them?

tradzero commented 6 years ago

user and merchant have same identifier. when user got token, Auth::guard('merchant')->check() will still pass when use user's token

llioor commented 6 years ago

@tradzero This is the solution #825 , thanks for @mtpultz for the answer

Efrat19 commented 5 years ago

@mtpultz you saved me an awful lot of time thanks!!

antonioreyna commented 4 years ago

hello guys, im getting this error, any idea? Auth guard driver [api] is not defined.

stale[bot] commented 3 years ago

Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.