preface

Whether it is an enterprise project or an individual project, an excellent system must have the ability of authentication. Authentication refers to verifying whether users have the right to access the system.

There are many front-end Authentication methods, such as HTTP Basic Authentication, session-cookie, OAuth (open authorization), and JWT

This chapter simulates a relatively complete front and back end coordination authentication scheme (using JWT authentication concept) through node.js Vue as the framework.

❗ PS: This chapter will not explain the basic knowledge too much, mainly for the application of technical code demonstration


Demand analysis

  • If the login succeeds, the backend returnsTokenvalue
  • tokenMust have a time limit, expired will not be valid
  • When the front end calls the interface, it needs to carrytokenTo access
  • Before switching to a page requiring permissions, check whether you have logged in and whether the login has expired

Technology stack

Vue, Node.js as the front and back end development framework

  • Axios serves as the HTTP library for the request interface
  • Express serves as a framework for interface development
  • Node. Js library
    • Body-parser – Handles POST data
    • Bcrypt – Password encryption and decryption match
    • jwt
      • Jsonwebtoken – Generates tokens
      • Passport, passport- jwt-resolve token
  • Jwt-decode – Front-end parsing token Obtain the validity time by token
  • Mongoose Connects to the Mongo database

Node.js background development

Initialize Express

Initialize our framework, introduce the required dependency libraries, and get ready for any functional development

😊 Tips: Mongoose’s operation will not be analyzed; Code for key functions is highlighted

  • App.js entry file
const express = require('express') / / into the express
const app = express() // Instantiate Express
const mongoose = require('mongoose') / / into the mongoose
const db = require('./config/mongokey.js').mongoURI // Import the database path
const bodyParser = require('body-parser') // Body-parser is used to handle POST requests
const passport = require('passport') // Function: parse token

const port = process.env.PORT || 5000 // Set the port number to 5000 locally

/ / test
// app.get('/', (req, res) => {
// res.send('Test,please ignore! ')
// })

// Connect to the database
mongoose.connect(db, { useNewUrlParser: true.useUnifiedTopology: true }).then((a)= > {
    // success
    console.log('Mongo Connect Successful')
}).catch((e) = > {
    // fail
    console.log('Mongo Connect fail')
})
mongoose.set('useFindAndModify'.false) // Disable useFindAndModify discard warning

// Use body-parser middleware to process POST data requests
app.use(bodyParser.urlencoded({
    extended: false
}))
app.use(bodyParser.json())

// Initialize passport parse token (for passport configuration, go to 👇)
app.use(passport.initialize())
require('./config/passport')(passport)

// ---- CORS setHeader cross domain Settings ----
app.use((req, res, next) = > {
    res.header("Access-Control-Allow-Origin"."*");
    res.header("Access-Control-Allow-Headers"."Content-Type,Authorization");
    res.header("Access-Control-Allow-Methods"."PUT,POST,GET,DELETE,OPTIONS");
    next();
})

/** * import routing table & use routing * @users user related */
const users = require('./routes/Api/users')
app.use('/hdgc/users', users)


app.listen(port, () => {
    console.log('❤ Server running on port${port}❤ `)})Copy the code
  • ./config/passport
/** * Passport configuration file * @import passport- JWT * @strategy * @extractjwt * @options jwtFromRequest token, */ const Strategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const User = require('.. /models/User'{} options.jwtFromRequest = const options = {} options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken() options.secretOrKey ='secretKey'// The secretOrKey must be the same as the cryptographic name used to generate the token. The module. Exports = passport = > {/ * * * * @ @ jwt_payload request for contentdone*/ passport. Use (new Strategy(options, (jwt_payload,done) => {
        User.findById(jwt_payload.id).then(user => {
            if (user) {
                return done(null, user)
            }
            return done(null, false)}). Catch (err => {console.log(err)}))} Note: Passport is used on the interface. The passport parses the token carried in the request and checks whether the User existsCopy the code

The Api layer development

  • ./routes/Api/users
const express = require('express')
const router = express.Router()
const bcrypt = require('bcryptjs') // Encryption plug-in
const jsonwebtoken = require('jsonwebtoken') / / token is generated
const passport = require('passport') / / token
const User = require('.. /.. /models/User') // Introduce the data model

/** * User related login and registration interface * @json * -code: information code * -data: data * -messgae: prompt message * @Form verification is handled by the front end */
router.post('/register', (req, res) => {
    console.log(req.body)
        // 1- Checks whether the user name already exists in the database
    User.findOne({
        username: req.body.username
    }).then((user) = > {
        if (user) {
            return res.json({
                code: '1'.email: 'Username already exists'})}else {
            const newUser = new User({
                username: req.body.username,
                password: req.body.password
            })

            // use bcrypt to encrypt password
            bcrypt.genSalt(10, (err, salt) => {
                bcrypt.hash(newUser.password, salt, (err, hash) => {
                    // hash - Encrypted password
                    if (err) {
                        // Encrypt exception capture
                        res.json({
                            code: '1'.message: 'Password encryption exception caught:${err}`
                        })
                        return
                    }
                    newUser.password = hash

                    // Store the database
                    newUser.save().then(user= > {
                        res.json({
                            code: '0'.data: user,
                            message: 'register successful'
                        })
                    }).catch(err= > {
                        // Exception catch
                        res.json({
                            code: '1'.message: 'Exception catching:${err}`
                        })
                    })
                })
            })
        }
    })
})

router.post('/login', (req, res) => {
    console.log(req.body)
    const username = req.body.username
    const password = req.body.password
        // Check whether the current user exists
    User.findOne({
        username: username
    }).then((user) = > {
        if(! user) {return res.json({
                code: '1'.message: 'Current user not registered'})}// Use bcrypt to decrypt the encrypted password
        bcrypt.compare(password, user.password).then(isMatch= > {
            if (isMatch) {
                // The match is successful
                const rule = {
                        id: user.id,
                        username: user.username
                    }
                    /** * JsonWebToken parameter Meaning * @rule * @encryption name - This name must be the same as the secretOrKey configured for the Passport * @expiration time * @arrow function * @return token */
                jsonwebtoken.sign(rule, 'secretKey', { expiresIn: 3600 }, (err, token) => {
                    if (err) {
                        // Token generation exception catch
                        res.json({
                            code: '1'.message: 'Token generation exception catch:${err}`
                        })
                        return
                    }
                    res.json({
                        code: '0'.data: user,
                        token: 'Bearer ' + token, // Must be preceded by 'Bearer '!!!!
                        message: 'Login successful'})})}else {
                // Failed to match
                return res.json({
                    code: '1'.message: 'Wrong username or password'})}})})})// To obtain the current user information, authentication is required!! Note that the Passport authentication policy is configured in passport
router.get('/', passport.authenticate('jwt', { session: false }), (req, res) => {
    User.findOne({
        username: req.user.username
    }).then((user) = > {
        if(! user) {return res.json({
                code: '1'.message: 'User information does not exist'
            })
        }
        req.user.password = '* * * * * *' // user passes in done() for passport. Note that password cannot appear in plain text
        res.json({
            code: '0'.data: req.user,
            message: 'success'})})})module.exports = router
Copy the code

Vue front desk development

Demand analysis

  • Axios encapsulates and implements request interception and response interception
  • Router Enables specific page authentication
  • Vuex manages the authentication status

Axios encapsulation and interception

import axios from 'axios'
import store from '.. /store'/** * Axios base configuration encapsulation (default configuration, Overwritable) * @baseurl Basic path (prefix) * @timeout Timeout time * @responseType Response data type (JSON) * @WithCredentials whether cookies are allowed * @header */ const Axios = axios.create({baseURL:'/',
    timeout: 10000,
    responseType: 'json',
    withCredentials: true,
    headers: {
        'Content-Type': 'application/json; charset=UTF-8'}}) // Request interception, once the interface is called, set loading in VUEX totrue, the page display Axios. Interceptors. Request. Use (config = > {store. Dispatch ('setLoading'.true)
    if (localStorage. Token) {/ / when the Token is in the cache, sets the Token to the Authorization request header config. The headers. The Authorization =localStorage.Token
    }
    returnConfig}, error => {// An error is reported when loading is updated tofalse
    store.dispatch('setLoading'.false)
    returnPromise.reject(error)}) // Response interception, once the interface returns, set loading in VUEX tofalse, the page display Axios. Interceptors. Response. Use (response = > {store. Dispatch ('setLoading'.false)
    returnResponse}, error => {// When loading is updated tofalse
    store.dispatch('setLoading'.false)
    return Promise.reject(error)
})


export default Axios
Copy the code
/** ** You can use * @userRegister interface * registerInfo registration form data * @userLogin login interface * loginInfo login form data * @getUserInfo to obtain the login person information - */ import Axios from cannot be called if this interface does not pass authentication'./http.js'

export function userRegister(registerInfo) {
    return Axios({
        url: '/hdgc/users/register',
        data: registerInfo,
        method: 'post',
        headers: {
            'Content-Type': 'application/json; charset=UTF-8'}})}export function userLogin(loginInfo) {
    return Axios({
        url: '/hdgc/users/login',
        data: loginInfo,
        method: 'post',
        headers: {
            'Content-Type': 'application/json; charset=UTF-8'}})}export function getUserInfo(username) {
    return Axios({
        url: '/hdgc/users/',
        data: username,
        method: 'get'})}Copy the code

BeforeEach () Implements page permission jumps

import Vue from 'vue'
import Router from 'vue-router'
import store from '.. /store';
import jwt_decode from 'jwt-decode'
import { Message } from 'element-ui'Vue.use(Router) // ... Route. beforeEach((to, from, next) => {// If you return to the home page, authentication is not requiredif (to.path == '/') {
        next()
    } else{/** * check whether the Token * @ exists. Then check whether the Token * @ does not existif (localStorage.Token) {/** * check whether the current Token is expired * @expired * @not expired */ const decoded = jwt_decode(localStorage.Token)
            const currentTime = Date.now() / 1000
            console.log('Token_Decode & currentTime', decoded, currentTime)
            if (decoded.exp < currentTime) {
                Vue.prototype.$notify({
                    title: 'Tips',
                    message: 'Token expired, log in again '.type: 'error',
                    duration: 3000
                })
                store.dispatch('clearCurrentState') // Empty vuex next('/')}else {
                next()
            }
        } else {
            Vue.prototype.$notify({
                title: 'Tips',
                message: 'Please login first! '.type: 'error',
                duration: 3000
            })
            store.dispatch('clearCurrentState') // Empty vuex next('/')}}})Copy the code

Interface invocation (emphasis on login interface)

signinClcik:function(){// loginInfo = userLogin(this.logininfo). Then (res => {if(res.data.code == 0){// Get token to store in cache. const token = res.data.token window.localStorage.setItem('Token',token) // Update authorization status this.$store.dispatch('setIsAuthenticated'.true)}else{
                    this.$notify({
                        title: 'Tips',
                        message: res.data.message,
                        type: 'error',
                        duration:3000
                    })
                }
            })
         },
Copy the code

Results show

  • Not logged in (the page cannot be redirected)

    ,

  • Login expires (here I set the expiration time to 5s)

  • Calling the authentication Interface (with token)

conclusion

The above JWT authentication scheme involves a coordination of the front and back ends, which is more suitable for enterprise-level and personal level project development, with strict authorization authentication and relatively simple technology stack involved. It is especially suitable for beginners to practice and build their own authentication system

If you like it, I hope dads like 😭