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.
- 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.
- Put token in cookie. The back end takes the token out of the cookie and updates the token before it expires.
- 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?).