jwt is used to implement validation in express framework.

Then the last article (using session to save user data) let jwt to save user data.
Passport-jwt/json webtoken is used here.
passport-jwt is a validation strategy of passport. It is validated using JWT (json web token).
JSON webtoken is a module for encoding, decoding and verifying jwt.

Using jwt to save user data and using session to save user data

session json web token
Save in server Save in client

Because session is saved in server, the server is under great pressure. When the concurrent volume reaches 1k, we can see the effect.
Because jwt is stored in client, it needs to be encrypted.

Using jwt

1. Installation dependency.

npm i passport-jwt jsonwebtoken

2. Create a configuration file that references the configuration to be used.

// ./config.js
module.exports = {
    secretKey: '12345-67890-9876-54321',
    mongoUrl: 'mongodb://localhost:27017/confusion'
}

3. Using database link configuration

var config = require('./config')
...
const url = config.mongoUrl
const connet = mongoose.connect(url, {useNewUrlParse: true, useCreateIndex: true})

4. Create validation files

./authenticate.js
var passport = require('passport'),
  LocalStrategy = require('passport-local').Strategy,
  User = require('./models/user')

var JwtStrategy = require('passport-jwt').Strategy,
  ExtractJwt = require('passport-jwt').ExtractJwt,
  jwt = require('jsonwebtoken')

var config = require('./config.js')

passport.use(new LocalStrategy(User.authenticate()))
passport.serializeUser(User.serializeUser())
passport.deserializeUser(User.deserializeUser())

exports.getToken = function (user) {
  return jwt.sign(user, config.secretKey, {expiresIn: 3600}) // Setting the timeout time for token issuance is 3600s
}

var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken() // Extracted from the validation header, the model defaults to `bearer'.
opts.secretOrKey = config.secretKey

exports.jwtPassport = passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
  console.log('JWT payload: ', jwt_payload)
  User.findOne({_id: jwt_payload._id}, (err, user) => {
    if (err) {
      return done(err, false)
    } else {
      if (user) {
        return done(null, user)
      } else {
        return done(null, false)
      }
    }
  })
}))

exports.verifyUser = passport.authenticate('jwt', {session: false}) // With jwt, session is no longer required to save user data.

5. When users apply for login, give jwt to the front end

// routes/users.js
...
var authenticate = require('../authticate')
router.post('/login', passport.authenticate('local'), (req, res) => { // Use passport-local for login
    var token = authenticate.getToken({_id: req.user._id}) // jwt after issuance
    res.statusCode = 200
    res.setHeader('Content-Type', 'application/json')
    res.json({success: true, token: token, status: 'You are successful logged in!'})
})

6. Store token on the front end

// use localStorage
$.ajax({
  type: 'post',
  dataType: 'json',
  url: 'users/login',
  data: {
    username: 'un',
    password: 'pw'
  },
  success: funciton (res) {
    localStorage.token = getToken(res)
    },
  error: funciton (err) {...}
})
// You can also use the vux method.
// You can also use the wrapped axios method.

7. User login timeout

After JSON webtoken verifies jwt, if the result does not pass, there will be three types of errors. Namely
Token Expired Error // Thrown when token timeout.

err = {
    name: 'TokenExpiredError',
    massage: 'jwt expired',
    expired: [ExpDate]
}

JsonWebTokenError
jwt error

err = {
    name: 'JsonWebTokenError',
    message: 'jwt malformed' // 'jwt malformed', 'jwt signature in required', 'invalid signature', 'jwt audience invalid. expected: [OPTIONS AUDIENCE]', 'jwt issuer invalid. expected: [OPTIONS ISSUER]', 'jwt id invalid. expected:[OPTIONS JWT ID]', 'jwt subject invalid. expected: [OPTIONS SUBJECT]'
}

NotBeforeError
This error is thrown when the current time exceeds the value of nbf.

err = {
    name: 'NotBeforeError',
    message: 'jwt not active',
    date: 2018-10-04T16:10:44.000Z
}

passport automatically sends "status code 401, content is Unauthorized" to the front end when it verifies that jwt does not pass.
When using passport/passport-jwt/json webtoken, no method of handling token expiration was found. So when passport-jwt validation fails, write another method to verify whether it is expired.

// authenicate.js
...
export.verifyUser = passport.authenticate('jwt', {
  session: false,
  failureRedirect: '/error/auth' // Unified handling of verification failures in this routing
  })
// routes/error.js
...
router.get('/auth', (req, res, next) => {
  let header = req.headers
  let rawToken = header.authorization
  if (!rawToken.split(' ').length) {
    res.json({ // Unified data structure facilitates front-end use
      code: 403,
      data: {},
      message: 'error for get token'
    })
  } else {
    let token = rawToken.split(' ')[1]
    jwt.verify(token, config.secretKey, err => { // JSON webtoken / config is used here. Note the citation
      switch (err.name) {
        case 'TokenExpiredError':
        case 'NotBeforeError':
          let payload = jwt.decode(token)
          token = authenticate.getToken({_id: payload._id})
          res.statusCode = 200
          res.setHeader('Content-Type', 'application/json')
          res.json({success: true, token: token, status: 'Has been refreshed token'})
          break
        case 'JsonWebTokenError':
        default:
          res.statusCode = 401
          res.json({
            code: 401,
            data: {
              error: err
            },
            message: 'token error'
          })
          break
      }
      })
  }
  })

8. User jwt authentication failed

passport automatically sends "status code 401, content is Unauthorized" to the front end when it verifies that jwt does not pass.

9. User application logout

Delete token at the front end.

10. Do not interrupt the operation of active users

In no.7, if the validation fails because the token expires, a new token is returned to the front end. It does not update the user's token without affecting the user's operation. The following summarizes several ways to update the user's token without affecting the user's operation.

  1. A timer is set at the front end. Request a new token from the back end and save it when it is less than the expiration time.
  2. Put token in cookie. The back end takes the token out of the cookie and updates the token before it expires.
  3. When token is stored in DB (e.g. Redis), the invalidation is deleted; however, an additional step is added to query the existence of token from DB at each checkout, which violates the stateless principle of JWT (isn't it the same as session?).

Keywords: node.js JSON Session encoding npm

Added by fourteen00 on Sat, 24 Aug 2019 11:07:48 +0300