神刀安全网

Securing React Redux Apps With JWT Tokens

JSON Web Token (JWT) is an open standard ( RFC 7519 ) that defines a simple way for securely transmitting information between client and server as a JSON object.

JWT Tokens V/S Cookies

Since HTTP is a stateless protocol, after you login (via username/password, OAuth 2 etc), for every future request to the server, you need to keep telling the server that you have already logged in so it can allow you do to authenticated/authorized actions. One way to do it is via “session” cookies and other way is to use “auth” tokens.

JWT is a way to generate auth tokens. It offers many benefits over using session cookies but the 2 major ones are:

  1. Server doesn’t need to ask DB to know who the user is because the user info is embedded inside the token itself! #performance!
  2. It works the same for both native mobile apps and browser clients. i.e. servers don’t need to implement two different mechanisms (browser v/s native).

Learn more: 10 things you should know about tokens and cookies

Note: You can click on the picture to zoom and read

Securing React Redux Apps With JWT Tokens

Note: I’ll be using the same blog-post Redux app in here as well.

Live App: https://protected-escarpment-79486.herokuapp.com

Source Code: https://github.com/rajaraodv/react-redux-blog

What Does A JWT Token look like?

The token has 3 parts: <header>.<payload>.<signature>

  1. Header : A Base64 encoded JSON that has info info that has info about algorithm(like HS256, RSA) used and so on.
  2. Payload: A Base64 encoded JSON that has info about the user.
  3. Signature : A String that was generated using #1 + #2 + “a secret” (that only the server knows), using the algorithm mentioned in #1.

Below is the picture from https://jwt.io that shows the JWT token used in our blog app in both encoded and decoded manners.

Securing React Redux Apps With JWT Tokens

As you can imaging, if the signature is valid and can be verified using the secret, we can simply decode the payload and get the user info w/o going to DB!

JWT Payload

Payload is just a JSON should contain anything that your app needs to identify user.

Typically this is:

  1. User Id, Username, Email, Image URL
  2. Any ACL like: isAdmin, isManager etc.

Note: JWT provides formal JSON keys like sub (subject = user), scope (string or array)that you can use to pass data as well (optional).

In the blog app, after the user logs in and tries to create a new post,we want to store authorId, authorName, authorUsername and authorImage along w/ Post’s own details. So I’m using those fields in payload. so when the user tries to create the post, we verify the token and get access to all of those fields related to user.

Implementing JWT Token In The Server

Irrespective of how the user signed up or logged in (via email, OAuth 2), all we need to do is:

  1. Generate JWT token and return it to the client
  2. Verify JWT token for protected routes in future requests

1. Generate JWT Token And Return It

Look at all the routes that users get authenticated. In our app user is authenticated when:

  1. User Signs Up (using email or Social network)
  2. User Signs In (after Sign up)
  3. Tries to Re-Authenticate using existing token (Browser Refresh)

1.1 Token Generator Function

Token is generated using useful fields from user model and secret (JWT_SECRET environment variable)

//Generate Token using secret from process.env.JWT_SECRET
var jwt = require(‘jsonwebtoken’);
function generateToken(user) {
//1. Dont use password and other sensitive fields
//2. Use fields that are useful in other parts of the
//app/collections/models
var u = {
name: user.name,
username: user.username,
admin: user.admin,
_id: user._id.toString(),
image: user.image
};
return token = jwt.sign(u, process.env.JWT_SECRET, {
expiresIn: 60 * 60 * 24 // expires in 24 hours
});
}

1.2 Generate JWT Token: Signup Route

router.post(‘/users/signup’, function(req, res, next) {
var body = req.body;
var hash = bcrypt.hashSync(body.password.trim(), 10);
var user = new User({
name: body.name.trim(),
username: body.username.trim(),
email: body.email.trim(),
password: hash,
admin: false,
isEmailVerified: false
});
user.save(function(err, user) { <-- Save User
if (err) throw err;
var token = utils.generateToken(user); <----- Generate Token
res.json({
user: user, <----- Return both user and token
token: token
});
});
});

1.3 Generate JWT Token: SignIn Route

router.post(‘/users/signin’, function(req, res) {
User
.findOne({username: req.body.username}) <-- Check username
.exec(function(err, user) {
if (err) throw err;
if (!user) {
return res.status(404).json({
error: true,
message: ‘Username or Password is Wrong’
});
}
bcrypt.compare(req.body.password, user.password, <-- check pwd         
function(err, valid) {
if (!valid) {
return res.status(404).json({
error: true,
message: ‘Username or Password is Wrong’
});
}

var token = utils.generateToken(user); <-- Generate token
user = utils.getCleanUser(user);
res.json({
user: user, <--- Return both user and token
token: token
});
});
});
});

1.4 Generate JWT Token: Re-Authenticate Route

This route is called when the user refreshes the browser to re-authenticate the user. The assumption here is that the app has stored the token in localstorage or sessionStorage .

How it works:

  1. When the browser is refreshed, the app checks if there is a token, if so, it asks the server to verify the token and send the user info back (as though the user just signed in)

2. The server receives the token, decodes it using secret (JWT_SECRET environment variable), if it’s valid,

//get current user from token
router.get(‘/me/from/token’, function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token;
if (!token) {
return res.status(401).json({message: ‘Must pass token’});
}
// Check token that was passed by decoding token using secret
jwt.verify(token, process.env.JWT_SECRET, function(err, user) {
if (err) throw err;
//return user using the id from w/in JWTToken
User.findById({
‘_id’: user._id
}, function(err, user) {
if (err) throw err;
user = utils.getCleanUser(user);
//Note: you can renew token by creating new token(i.e.    
//refresh it)w/ new expiration time at this point, but I’m
//passing the old token back.
// var token = utils.generateToken(user);
res.json({
user: user, <--- return both user and token
token: token
});
});
});
});

2. Verify JWT Token For Protected Routes

In the blog app, we are protecting creating posts and deleting posts from non authenticated user.

Easiest way to verify token is to do what we did in the re-authenticate route. But we need a middleware function to protect various routes. Thankfully, there is a popular npm module express-jwt to do just that.

var expressJwt = require(‘express-jwt’); //import

//create a new post
//Simply pass secret to expressJWt
router.post(‘/posts’, expressJwt({secret: process.env.JWT_SECRET}), function(req, res, next) {
});

Only requirement of express-jwt is to pass the token in the header as: Authorization: Bearer <token>

Front End: Storing And Using JWT In React Redux App

We need to implement:

  1. Sign In page
  2. Sign Up page
  3. Logout
  4. Browser Refresh

Implementing Sign Up and Sign In pages are similar to building any other pages. To keep this blog short, I suggest you go though:

  1. Blog: A Guide For Building A React Redux CRUD App
  2. Source code: https://github.com/rajaraodv/react-redux-blog

One new thing that’s different from regular CRUD app is implementing browser refresh.

Storing JWT Token

We need to store this token somewhere. We can store it as a client-side cookie or in a localStorage or sessionStorage. There are pros and cons in each option but for this app, we’ll store it in sessionStorage.

In our blog app, both SignUpFormContainer component and SignInFormContainer component contains code to store jwtToken in sessionStorage

dispatch(signUpUser(values))
.then((response) => {
let data = response.payload.data;
//if any one of these exist, then there is a field error 
if(response.payload.status != 200) {
//let other components know
dispatch(signUpUserFailure(response.payload));
reject(data); //this is for redux-form itself
} else {
//store JWT Token to browser session storage
//If you use localStorage instead of sessionStorage, then this w/
//persisted across tabs and new windows.
//sessionStorage = persisted only in current tab

sessionStorage.setItem(‘jwtToken’, response.payload.data.token);

//let other components know that we got user and things are fine
dispatch(signUpUserSuccess(response.payload));
resolve();//this is for redux-form itself
}
});

Brower Refresh — JWT ReAuthentication

The AppContainer component (parent component to all pages) implements the browser refresh code.

When the browser is refreshed,

  1. " loadUserFromToken ” is called.
  2. If there is no token, nothing happens
  3. If there is token, we call “meFromToken” action to ask server if the token is valid.
  4. If the token is valid, we store it back as jwtToken in sessionStorage.
  5. And let other components know by dispatching meFromTokenSuccess, this helps other components that depends on if the user is loggedIn vs not-loggedIn to display themselves properly.
const mapDispatchToProps = (dispatch) => {
return {
loadUserFromToken: () => {
let token = sessionStorage.getItem(‘jwtToken’);
if(!token || token === ‘’) {//if there is no token, dont bother
return;
}
//fetch user from token (if server deems it’s valid token)
dispatch(meFromToken(token))
.then((response) => {
if (!response.error) {
//store token
sessionStorage.setItem(‘jwtToken’, response.payload.data.token);
dispatch(meFromTokenSuccess(response.payload))
} else {
//remove token from storage
sessionStorage.removeItem(‘jwtToken’);
dispatch(meFromTokenFailure(response.payload));
}
});
},
resetMe: () =>{ // logout
sessionStorage.removeItem(‘jwtToken’); //remove token from storage
dispatch(resetToken());
}
}
}

That’s it!

Other Blogs In This Series :

  1. Step by Step Guide To Building React Redux Apps
  2. A Guide For Building A React Redux CRUD App (3-page app)
  3. Using Middlewares In React Redux Apps
  4. Adding A Robust Form Validation To React Redux Apps
  5. Securing React Redux Apps With JWT Tokens

Live App: https://protected-escarpment-79486.herokuapp.com

Source Code: https://github.com/rajaraodv/react-redux-blog

:tada: If you like this post, please ❤❤❤ it below and share it on twitter ( https://twitter.com/rajaraodv ):tada:

Thanks for reading!!:grinning::+1:

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Securing React Redux Apps With JWT Tokens

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮