JSON Web Token (JWT) - Thực hành sử dụng refresh token khi token hết hạn với nodejs và express js
JSON Web Token là gì?
Creating the Project
AnonyStick$ express --view=ejs refreshToken-d
create : refreshToken-demo/
create : refreshToken-demo/public/
create : refreshToken-demo/public/javascripts/
create : refreshToken-demo/public/images/
create : refreshToken-demo/public/stylesheets/
create : refreshToken-demo/public/stylesheets/style.css
create : refreshToken-demo/routes/
create : refreshToken-demo/routes/index.js
create : refreshToken-demo/routes/users.js
create : refreshToken-demo/views/
create : refreshToken-demo/views/error.ejs
create : refreshToken-demo/views/index.ejs
create : refreshToken-demo/app.js
create : refreshToken-demo/package.json
create : refreshToken-demo/bin/
create : refreshToken-demo/bin/www
change directory:
$ cd refreshToken-demo
install dependencies:
$ npm install
run the app:
$ DEBUG=refreshtoken-demo:* npm start
Ở đây tôi tạo name project là refreshToken-demo
và sử dụng template là ejs
không giải thích kỹ nha, vì ở đây qua dễ rồi.
Creating Server and adding routes
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var app = express();
//add them
var bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
-
usersRouter
đây là nơi chứa dữ liệu của Users. Do đó chỉ những người login thành công và có quyền mới có thể lấy được data này thông qua api dựa vào token. -
indexRouter
đây là nơi mà router sẽ khai báo API login và API lấy lại token nếu như token hết hạn phải sử dụng refreshToken.
Create files
### /router/index.js
var express = require('express');
var router = express.Router();
const _CONF = require('../config')
var jwt = require('jsonwebtoken')
var refreshTokens = {} ;// tao mot object chua nhung refreshTokens
/* GET home page. */
router.get('/', function(req, res, next) {
return res.json({status: 'success', elements: 'Hello anonystick'})
});
/* LOGIN . */
router.post('/login', function(req, res, next) {
const {username, password} = req.body;
if(username === 'anonystick.com' && password === 'anonystick.com'){
let user = {
username: username,
role: 'admin'
}
const token = jwt.sign(user, _CONF.SECRET, { expiresIn: _CONF.tokenLife }) ;//20 giay
const refreshToken = jwt.sign(user, _CONF.SECRET_REFRESH, { expiresIn: _CONF.refreshTokenLife})
const response = {
"status": "Logged in",
"token": token,
"refreshToken": refreshToken,
}
refreshTokens[refreshToken] = response
return res.json(response)
}
return res.json({status: 'success', elements: 'Login failed!!!'})
})
/* Get new token when jwt expired . */
router.post('/token', (req,res) => {
// refresh the damn token
const {refreshToken} = req.body
// if refresh token exists
if(refreshToken && (refreshToken in refreshTokens)) {
const user = {
username: 'anonystick.com',
role: 'admin'
}
const token = jwt.sign(user, _CONF.SECRET, { expiresIn: _CONF.tokenLife})
const response = {
"token": token,
}
// update the token in the list
refreshTokens[refreshToken].token = token
res.status(200).json(response);
} else {
res.status(404).send('Invalid request')
}
})
module.exports = router;
### /routes/users.js
var express = require('express');
var router = express.Router();
router.use(require('../middleware/checkToken'))
/* GET users listing. */
router.get('/', function (req, res) {
const users = [{
username: 'Cr7',
team: 'Juve',
}, {
username: 'Messi',
team: 'Barca',
}]
res.json({ status: 'success', elements: users })
})
module.exports = router;
### config/index.js
const config = Object.freeze({
SECRET:"SECRET_ANONYSTICK",
SECRET_REFRESH: "SECRET_REFRESH_ANONYSTICK",
tokenLife: 10,
refreshTokenLife: 120
})
module.exports = config;
### middleware/checkToken.js
const jwt = require('jsonwebtoken')
const _CONF = require('../config/')
module.exports = (req, res, next) => {
const token = req.body.token || req.query.token || req.headers['x-access-token']
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, _CONF.SECRET, function(err, decoded) {
if (err) {
console.error(err.toString());
//if (err) throw new Error(err)
return res.status(401).json({"error": true, "message": 'Unauthorized access.', err });
}
console.log(`decoded>>${decoded}`);
req.decoded = decoded;
next();
});
} else {
// if there is no token
// return an error
return res.status(403).send({
"error": true,
"message": 'No token provided.'
});
}
}
Nhìn vào đoạn code thì không khó để hiểu nhưng ở đây tôi muốn bạn chú ý đến những đoạn code sau:
const token = jwt.sign(user, _CONF.SECRET, { expiresIn: _CONF.tokenLife }) ;//20 giay
const refreshToken = jwt.sign(user, _CONF.SECRET_REFRESH, { expiresIn: _CONF.refreshTokenLife})
Khi một tài khoản login thành công thì hệ thống sẽ sinh ra hai token đó là token và refreshToken. Đương nhiên thời gian sống của hai token này khác nhau vì sao thì tôi có nói trong bài viết trước kia. Và người đó sẽ nhận được và lưu trữ ở client.
const response = {
"status": "Logged in",
"token": token,
"refreshToken": refreshToken,
}
refreshTokens[refreshToken] = response
Và chúng tôi sẽ lưu trữ những refreshToken trên server dùng vào nhiều mục đích khác nhau như lấy lại token nếu hết hạn, hoặc chặn ngay những hành động hacker thì chiếm đoạt token của User.
Tips: Tốt hơn hết bạn nên lưu trữ refreshToken ở redis vì khi reload server thì nó sẽ mất
router.use(require('../middleware/checkToken'))
Tiếp theo là tôi tạo một file middleware có tác dụng check token hợp lệ trước khi truy cập tài nguyên, nhìn vào bạn sẽ rõ hơn.
router.use(require('../middleware/checkToken'))
/* GET users listing. */
router.get('/', function (req, res) {
const users = [{
username: 'Cr7',
team: 'Juve',
}, {
username: 'Messi',
team: 'Barca',
}]
res.json({ status: 'success', elements: users })
})
Và nếu token không hợp lệ đương nhiên bạn không thể truy cập vào tài nguyên Users được đâu. Run code Sau khi clone code về bạn có thể dùng command để run như sau:
AnonyStick$ npm start
Bạn có thể nhìn thấy chúng ta không thể truy cập được. Bạn cũng có thể thử với một token tùm lum nào đó. Tiếp theo tôi sẽ login với tài khoản là Anonystick
Khi login thành công thì client sẽ nhận được 2 token bao gồm token
và refreshToken
Sau đó bạn sử dụng token
để truy cập vào list user.
Sau thời gian mà chúng ta đã setting trong file config thì token sẽ hết hạn như thế này.
Khi hết hạn token thì chúng ta sẽ sử dụng post /token để lấy lại token mới sử dụng refreshToken mà login đã có
Tóm lại
Về cơ bản thì cơ chế lấy lại token khi hết hạn khi dùng jwt là như vậy. Nhưng ở đây chi là quy trình khác với thực thế ở những chỗ sau như, khi hết hạn thì client tự động gửi refreshToken về server để lấy chứ không phải mình đi copy như vậy. Client phải tự động nhận được lỗi 401 invalid token và tự động gọi function làm mới token rồi chạy tiếp như chưa có chuyện gì xảy ra. Có thể ở bài viết sau tôi sẽ làm những đieuf này cho các bạn.
Nhận xét