Recently, I am working on a project of a company, using vue. js in the front end and Laravel in the back end to build Api service. I originally intended to use Laravel Passport for user authentication, but felt it was a bit troublesome, so I used JwT-Auth.


The installation

The latest version of JWT-Auth is 1.0.0 RC.1 and already supports Laravel 5.5. If you are using Laravel 5.5, you can use the following command to install it. As suggested by @tradZero brothers in the comments section, if you are Laravel 5.5 or lower, it is also recommended to use the latest version, the previous version of RC.1 has multi-user token authentication security issues.

$ composer require tymon/jwt-auth 1.0. 0-rc1.
Copy the code


configuration


Add the following line to the providers array of the config/app.php file:

app.php

'providers' => [

    ...

    Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]
Copy the code


Publishing configuration files

Publish the jwt-Auth configuration file by running the following command in your shell:

shell

$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Copy the code

This command generates a jwt.php configuration file in the config directory where you can customize the configuration.


Generate the key

Jwt-auth already has a predefined Artisan command for you to generate Secret. Just run the following command in your shell:

shell

$ php artisan jwt:secret

Copy the code

This command adds a new line JWT_SECRET=secret to your.env file.


Configuration Auth guard

In the config/auth.php file you need to update Guards/Driver to JWT:

auth.php

'defaults'= > ['guard'= >'api'.'passwords'= >'users',],...'guards'= > ['api'= > ['driver'= >'jwt'.'provider'= >'users',]],Copy the code

Only available with Laravel 5.2 and above.


To change the Model

If we need to use JwT-Auth as User authentication, we need to make a small change to our User model and implement an interface. The changed User model is as follows:

User.php


      

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    // Rest omitted for brevity

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier(a)
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims(a)
    {
        return[]; }}Copy the code


Details about configuration items

jwt.php


      

return [

    /* |-------------------------------------------------------------------------- | JWT Authentication Secret | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | generated token is used to encrypt the secret of | * /

    'secret' => env('JWT_SECRET'),

    /* |-------------------------------------------------------------------------- | JWT Authentication Keys | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | if you are in the env file defines JWT_SECRET | so random strings JWT will use symmetric algorithm to generate the token | if you don't have, then the JWT will use the following configuration of public and private keys to generate a token | * /

    'keys'= > [/* |-------------------------------------------------------------------------- | Public Key | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | | the public key * /

        'public' => env('JWT_PUBLIC_KEY'),

        /* |-------------------------------------------------------------------------- | Private Key | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | | * / of the private key

        'private' => env('JWT_PRIVATE_KEY'),

        /* |-------------------------------------------------------------------------- | Passphrase | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | the private key password. If no value is set, it can be null. | * /

        'passphrase' => env('JWT_PASSPHRASE')],/* |-------------------------------------------------------------------------- | JWT time to live | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | valid access_token specified length of time (in minutes) for the unit, the default is 1 hour, You can also set it to null, to produce tags | * / never expire

    'ttl' => env('JWT_TTL'.60),

    /* |-------------------------------------------------------------------------- | Refresh time to live | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | specified access_token can refresh the length of time (in minutes). The default duration is 2 weeks. | probably means if the user has an access_token, so he can bring his access_token | come to receive new access_token, until two weeks later, he can't continue to refresh, you need to log in. | * /

    'refresh_ttl' => env('JWT_REFRESH_TTL'.20160),

    /* |-------------------------------------------------------------------------- | JWT hashing algorithm | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | specified will be used for hashing algorithm for token to sign. | * /

    'algo' => env('JWT_ALGO'.'HS256'),

    /* |-------------------------------------------------------------------------- | Required Claims | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | specified must exist in any statement of the token. | | * /

    'required_claims'= > ['iss'.'iat'.'exp'.'nbf'.'sub'.'jti',]./* |-------------------------------------------------------------------------- | Persistent Claims | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | specified when the refresh token key to retain statement. | * /

    'persistent_claims'= > [// 'foo',
        // 'bar',]./* |-------------------------------------------------------------------------- | Blacklist Enabled | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | in order to make the token is invalid, you must enable the blacklist. | if you don't want or don't need this function, please set it to false. | * /

    'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED'.true),

    /* | ------------------------------------------------------------------------- | Blacklist Grace Period | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | when the number of concurrent requests using the same JWT, | as access_token refresh, some of which might fail | set the request time in seconds to prevent the failure of concurrent requests. | * /

    'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD'.0),

    /* |-------------------------------------------------------------------------- | Providers | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | to specify the entire package used in the various providers. | * /

    'providers'= > [/* |-------------------------------------------------------------------------- | JWT Provider | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | specified is used to create and decode the token of the provider. | * /

        'jwt' => Tymon\JWTAuth\Providers\JWT\Namshi::class,

        /* |-------------------------------------------------------------------------- | Authentication Provider | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | designated for providers to authenticate a user. | * /

        'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,

        /* |-------------------------------------------------------------------------- | Storage Provider | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | designated provider for storing markup in the blacklist. | * /

        'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,

    ],

];
Copy the code


Custom authentication middleware

To explain what I want to achieve, I want the user to provide an account and password to log in. If the login is successful, THEN I issue an Access _token to the front end, set in the header to request a route that requires user authentication.

Meanwhile, I hope that if the user’s token expires, I can pass this request temporarily, refresh the user’s ACCESS _token in this request, and finally return the new ACCESS _token to the front end in the response header, so that the access _token can be refreshed painlessly. Users get a good experience, so start coding.

Execute the following command to create a new middleware:

php artisan make:middleware RefreshToken
Copy the code

The middleware code is as follows:

RefreshToken.php


      

namespace App\Http\Middleware;

use Auth;
use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

// Note that we want to inherit from JWT BaseMiddleware
class RefreshToken extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     *
     * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // Check if there is a token in this request and throw an exception if there is no token.
        $this->checkForToken($request);

       // Use the try package to catch a TokenExpiredException thrown by token expiration
        try {
            // Check the login status of the user. If the status is normal, the user passes
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth'.'Not logged in');
        } catch (TokenExpiredException $exception) {
          // We caught a TokenExpiredException thrown by the token expiration. What we need to do here is refresh the user's token and add it to the response header
            try {
                // Refresh the user's token
                $token = $this->auth->refresh();
               // Use a one-time login to ensure the success of the request
                Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
            } catch (JWTException $exception) {
               // If this exception is caught, refresh is also expired and the user cannot refresh the token and needs to log in again.
                throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage()); }}// Return the new token in the response header
        return $this->setAuthenticationHeader($next($request), $token); }}Copy the code


Set up the Axios interceptor

The HTTP request suite I chose was Axios. In order to refresh the token painlessly, we need to define an interceptor for Axios to receive our refreshed token as follows:

app.js

import Vue from 'vue'
import router from './router'
import store from './store'
import iView from 'iview'
import 'iview/dist/styles/iview.css'

Vue.use(iView)


new Vue({
    el: '#app',
    router,
    store,
    created() {
        // Custom AXIos response interceptor
        this.$axios.interceptors.response.use((response) = > {
            // Check if there is a token in the response. If so, use the token to replace the local token. You can write your own logic to update tokens based on your business needs
            var token = response.headers.authorization
            if (token) {
                // If there is a token in the header, the refreshToken method is triggered to replace the local token
                this.$store.dispatch('refreshToken', token)
            }
            return response
        }, (error) => {
            switch (error.response.status) {
                
                // If the HTTP code in the response is 401, then the user's token may have expired or something, I can trigger the logout method to clear up the local data and redirect the user to the login page
                case 401:
                    return this.$store.dispatch('logout')
                    break
                // If the HTTP code in the response is 400, an error message is displayed to the user
                case 400:
                    return this.$Message.error(error.response.data.error)
                    break
            }
            return Promise.reject(error)
        })
    }
})

Copy the code

The code in Vuex is as follows:

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        name: null.avatar: null.mobile: null.token: null.remark: null.auth: false,},mutations: {
        // The user logs in successfully, stores the token and sets the header
        logined(state, token) {
            state.auth = true
            state.token = token
            localStorage.token = token
        },
        // The user successfully refreshed the token and replaced the local token with the new token
        refreshToken(state, token) {
            state.token = token
            localStorage.token = token
            axios.defaults.headers.common['Authorization'] = state.token
        },
        // After a successful login, pull the user information and save it to the local computer
        profile(state, data) {
            state.name = data.name
            state.mobile = data.mobile
            state.avatar = data.avatar
            state.remark = data.remark
        },
        // The user logs out and clears local data
        logout(state){
            state.name = null
            state.mobile = null
            state.avatar = null
            state.remark = null
            state.auth = false
            state.token = null

            localStorage.removeItem('token')}},actions: {
         // Save the user information after successful login
        logined({dispatch,commit}, token) {
            return new Promise(function (resolve, reject) {
                commit('logined', token)
                axios.defaults.headers.common['Authorization'] = token
                dispatch('profile').then((a)= > {
                    resolve()
                }).catch((a)= > {
                    reject()
                })
            })
        },
        // After a successful login, use the token to pull the user information
        profile({commit}) {
            return new Promise(function (resolve, reject) {
                axios.get('profile', {}).then(respond= > {
                    if (respond.status == 200) {
                        commit('profile', respond.data)
                        resolve()
                    } else {
                        reject()
                    }
                })
            })
        },
        // The user logs out, clears local data and redirects to the login page
        logout({commit}) {
            return new Promise(function (resolve, reject) {
                commit('logout')
                axios.post('auth/logout', {}).then(respond= > {
                    Vue.$router.push({name:'login'})})})},// Save the refreshed token locally
        refreshToken({commit},token) {
            return new Promise(function (resolve, reject) {
                commit('refreshToken', token)
            })
        },
    }
})

Copy the code


Update handlers for exception handling

Because of our building is API service, so we need to update my app/Exceptions/Handler render in PHP

Method, custom handle some exceptions.

Handler.php


      

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class Handler extends ExceptionHandler
{.../**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Exception $exception
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $exception)
    {
        // Parameter validation error exception, we need to return 400 HTTP code and an error message
        if ($exception instanceof ValidationException) {
            return response(['error' => array_first(array_collapse($exception->errors()))], 400);
        }
        // User authentication exception, we need to return 401 HTTP code and error message
        if ($exception instanceof UnauthorizedHttpException) {
            return response($exception->getMessage(), 401);
        }

        return parent::render($request, $exception); }}Copy the code

After updating this method, exceptions thrown in our custom middleware above and exceptions thrown in our parameter validation error below will be converted to the specified format.


use

Now we can add a few new routes to our routes/api.php routing file to test:

api.php

Route::prefix('auth')->group(function($router) {
    $router->post('login'.'AuthController@login');
    $router->post('logout'.'AuthController@logout');


});

Route::middleware('refresh.token')->group(function($router) {
    $router->get('profile'.'UserController@profile');
});
Copy the code

Run the following command in your shell to add a controller:

$ php artisan make:controller AuthController

Copy the code

Open the controller and write the following


      

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Transformers\UserTransformer;

class AuthController extends Controller
{

    /**
     * Get a JWT token via given credentials.
     *
     * @param  \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login(Request $request)
    {
        I have changed the login user name and used the mobile phone number to log in
        $rules = [
            'mobile'= > ['required'.'exists:users',].'password'= >'required|string|min:6|max:20',];// Validates the argument. If validation fails, a ValidationException is thrown
        $params = $this->validate($request, $rules);

	   If the login is successful, the code and token of 201 will be returned. If the login fails, the code and token of 201 will be returned
        return ($token = Auth::guard('api')->attempt($params))
            ? response(['token'= >'bearer ' . $token], 201)
            : response(['error'= >'Wrong account or password'].400);
    }

    /** * Handle user logout logic **@return \Illuminate\Http\JsonResponse
     */
    public function logout(a)
    {
        Auth::guard('api')->logout();

        return response(['message'= >'Exit successful']); }}Copy the code

Then we go to Tinker:

$ php artisan tinker
Copy the code

To create a test user, run the following command. My user name is a mobile phone number, you can replace it with an email. Don’t forget to set up the namespace:

>>> namespace App\Models;

>>> User::create(['name' => 'Test','mobile' => 17623239881,'password' => bcrypt('123456')]);

Copy the code

The correct execution result is shown as follows:

Then open Postman to test the API

The correct request result is shown below:

It can be seen that we have successfully obtained the token. Next, let’s verify and refresh the token

As you can see in the figure, we’ve got the new token, and then the axios interceptor we set up earlier takes care of everything, which replaces the local token with this token.


Version of the popular science

Feel quite many people have no concept of version, so here popular science under the common version.

  • Alpha version

    This release indicates that the Package is only a preliminary completion, usually communicated within developers, with a limited release to professional testers. Generally speaking, this version of the software bugs more, ordinary users had better not install.

  • The Beta version

    This is a major improvement over Alpha and fixes serious bugs, but there are still some flaws that need to be further ironed out through extensive release testing. After testing by some professional amateurs, the results are fed back to the developers, who then make targeted modifications. This version is also not suitable for the general user installation.

  • RC/Preview version

    RC, short for Release Candidate, is a standing term that means the final Release is ready. In general, the RC version has all the features and most of the bugs removed. Generally the Package author will only fix bugs at this stage and will not make any major changes to the software.

  • General distribution

    Usually after the previous three versions, the author will release this version. This version fixes most of the bugs and will be maintained for a while. (The timing is based on the author’s wishes; for example, the general release of Laravel provides a year of maintenance support.)

  • LTS (Long Term Support) version

    This version is a special version, and the normal version is designed to support longer than normal hours. (Laravel’s LTS version, for example, provides three years of maintenance support.)


conclusion

Jwt-auth is indeed a great user authentication Package, simple to configure and easy to use.

That’s all. Thanks for reading.