I have been having trouble on my MERN application I'm building. I am storing a token in cookies when I register/login a user. I am able to access this cookie through my express app fine and when using Postman all works as well.
Now the issue that I am encountering is when I try to access protected routes through my client side that is in React. I'm not sure how to handle this correctly because in my express app I can't get the cookie in the same matter I am doing so when using postman for example. I am using httpOnly: true so that the cookie can only be access from my express app. I want to keep this the way it is with httpOnly for security reasons so I do not want access to the token through my client side.
Here is my code on the express app...
exports.protect = catchAsync(async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
token = req.headers.authorization.split(' ')[1];
}
console.log(req.headers);
if (!token) {
return next(new AppError('No token found!', 401));
}
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
const freshUser = await User.findById(decoded.id);
if (!freshUser) {
return res.status(401).json({
status: 'fail',
message: 'This token no longer exists!',
});
}
req.user = freshUser;
next();
});
When I try to access the token using req.headers.authorization using postman it works but like I mentioned before using my client side react app it doesn't and is undefined. using req.headers I can see cookies though. I feel like I can just access req.headers.cookies then but what should I do when just running my backend without running my client side app? I do not want to have separate code. Please let me know if I need to clarify what I am saying here.
Got some extent of your question.
First your node server and your react server won't be running on a same port. So, server cookies won't work like it normally should. You should set domain as well. And if your react server and node server run on that domain and it's domain then they can share the cookie.
Example of sending request to node server with authorization header through axios:
axios.post("https://<api>.com", {
headers: {
Authorization: 'Bearer <your-bearer-token>'
}
})
postman sends it this way.
Related
So i was learning about JWT authentication for user login in MERN project using cors npm.I am able to generate the jwt and also able to store it in the DB , but i am not able to see my cookie being stored in the browser.
This is my generateAuthToken() function present in the schema file in express server, here i am generating a jwt & returning it -
userSchema.methods.generateAuthToken = async function(){
try{
//generate token
const token = jwt.sign({_id:this._id.toString()} , process.env.SECRET_KEY);
//stored the generated token in our document
this.tokens = this.tokens.concat({token : token});
//saved the changes made in the document
await this.save();
//returning the token generated when called
return token;
}catch(err){
console.log(err);
}
}
and calling the generateAuthtoken() function in the index.js present in express server.The returned token is stored in the 'token' -
const token = await userLogin.generateAuthToken();
//console.log(token);
//storing logged in tokens inside cookies
res.cookie("logintoken" , token , {
expires : new Date(Date.now() + 60000), //60sec from the time we store the token inside the cookie
httpOnly : true
});
And in the frontend react code , i am sending a credentials header inside fetch api , which calls to login route present in my express server at port 4000 -
const response = await fetch('http://localhost:4000/login',{
method : "POST",
credentials: 'include',
headers : {
"Content-Type" : "application/json"
},
body : JSON.stringify({
email : email,
password : password
})
});
I am using cors npm package for cross-region requests in my express server -
const cors = require('cors');
app.use(cors());
And in my browser console ,i am facing this error -
but when i check in my browser , cookie is not getting stored in the storage -
To summarise , i am generating a jwt and returning it inside the function.Then storing that returned token inside a cookie named 'logintoken'.But that cookie is not getting stored in the browser.
I was researching on this and was able to find some threads stating that we also have to define some headers in cors so that the browser is able to store the cookie.But i was not able to figure out how to add as well as which headers are required to achieve this.I have worked before with cookies & jwt , but this is my first time learning with react , express & cors npm.Thank you for your response.
here is a code snippet from my current app
app.use(
cors({
credentials: true,
origin: `http://${process.env.REACT_APP_SERVER_HOST}:${process.env.PORT}`,
})
);
The long short is...
My express backend sets a cookie and sends it back to the client, everything works great when I use Postman and my React Native app. The issue is the web version which is done via ReactJs makes the same request but cookie is stored in the browser.
So I know cookies are working but not for the web version which is strange when I created a test endpoint http://localhost:3000/server and call it straight from the browser the cookie is stored.
So this is my code doing a simple fetch to the server which then sends back a cookie:
const fetchData = async () => {
try {
const res = await fetch(`http://192.168.0.11:3000/server/`, {
credentials: "include",
});
if (res.ok) {
const resData = await res.json();
console.log({ resData });
}
} catch (err) {
console.log({ err });
}
};
The request came back successful but no cookie is stored
Access the same endpoint from the browser directly results in this:
An extract from the response header while being sent shows that the cookie was sent in the response just not stored in the frontend
Was a pretty simple fix
I was using http://localhost:3001 for the react app I just simply used the ip address instead http://192.168.0.11:3001
I need to know that if my authentication and session management method is right.
I am using session management as when I receive successful auth. from node server. I store user data(without any trace of pass.) in $window.sessionStorage and if user marked rememberMe(checkbox), store data in $window.localStorage too.
Through this I am able to get data in different controllers. Though I read somewhere about session implementation at server(nodeJs) side is also possible. But I am not sure about how to use session along with JSONToken Authentication.
I was using
https://jasonwatmore.com/post/2015/12/09/MEAN-Stack-User-Registration-and-Login-Example.aspx
as a learning example but I could not understand it.
/app/app.js
Why is it in the run() method ?
// add JWT token as default auth header
$http.defaults.headers.common['Authorization'] = 'Bearer ' + $window.jwtToken;
and what is this:
// manually bootstrap angular after the JWT token is retrieved from the server
$(function () {
// get JWT token from server
$.get('/app/token', function (token) {
window.jwtToken = token;
angular.bootstrap(document, ['app']);
});
});
/controllers/app.controller.js
// use session auth to secure the angular app files
router.use('/', function (req, res, next) {
if (req.path !== '/login' && !req.session.token) {
return res.redirect('/login?returnUrl=' + encodeURIComponent('/app' + req.path));
}
next();
});
// make JWT token available to angular app
router.get('/token', function (req, res) {
res.send(req.session.token);
});
// serve angular app files from the '/app' route
router.use('/', express.static('app'));
So using a session server-side with JWT kind of defeats the purpose of using JWT. JWT's are awesome in a number of ways, but one of the ways they are great, is regardless which server intercepts a request, they can verify the user.
If you put it in a session, you have to make sure the client keeps going to the same server as the session is saved in memory on that machine. There are plenty of ways around that, but again it kind of defeats the purpose of a JSON web token.
What I did for my authentication with angular/node/JWT was just passed the JWT back in the header every time, and with my middleware intercepted it with:
req.header.whatever_my_tokens_name_is
The code below set the $http to send on every request the JWT Token to the server.
// add JWT token as default auth header
$http.defaults.headers.common['Authorization'] = 'Bearer ' + $window.jwtToken;
The code below get the token from '/app/token' and store it in LocalStorage. After that, it starts the angular.
// manually bootstrap angular after the JWT token is retrieved from the server
$(function () {
// get JWT token from server
$.get('/app/token', function (token) {
window.jwtToken = token;
angular.bootstrap(document, ['app']);
});
});
Here this is a middleware that check if there are no token stored in req.session.token and requested url is not '/login'. If so, send a redirect to '/login'.
// use session auth to secure the angular app files
router.use('/', function (req, res, next) {
if (req.path !== '/login' && !req.session.token) {
return res.redirect('/login?returnUrl=' + encodeURIComponent('/app' + req.path));
}
next();
});
Finally here, this is a endpoint to the client request the '/token' again from the server.
// make JWT token available to angular app
router.get('/token', function (req, res) {
res.send(req.session.token);
});
Anyway, check the #morgan-g response regarless session-side and JWT.
I hope this helps.
I'm working on a MEAN application with authentication using JSON web tokens. basically on every request, I am checking to see if user has a valid token. if so they can go through to the route, otherwise they are returned to login page.
I want to make certain routes /admin/etc... only accessible to logged in users who are also admin. I have set up an isAdmin flag in mongo. I am new to nodejs and wondering what is the best way to check this. Do I do it on the angular side in routes? Or can I somehow create permission-based tokens on authentication? For reference, I am following the code from the MEAN Machine book, in particular here -
https://github.com/scotch-io/mean-machine-code/tree/master/17-user-crm
First, authorization decisions must be done on the server side. Doing it on the client side in Angular.js as you suggested is also a good idea, but this is only for the purpose of improving the user's experience, for example not showing the user a link to something they don't have access to.
With JWTs, you can embed claims about the user inside the token, like this:
var jwt = require('jsonwebtoken');
var token = jwt.sign({ role: 'admin' }, 'your_secret');
To map permissions to express routes, you can use connect-roles to build clean and readable authorization middleware functions. Suppose for example your JWT is sent in the HTTP header and you have the following (naive) authorization middleware:
// Naive authentication middleware, just for demonstration
// Assumes you're issuing JWTs somehow and the client is including them in headers
// Like this: Authorization: JWT {token}
app.use(function(req, res, next) {
var token = req.headers.authorization.replace(/^JWT /, '');
jwt.verify(token, 'your_secret', function(err, decoded) {
if(err) {
next(err);
} else {
req.user = decoded;
next();
}
});
})
With that, you can enforce your authorization policy on routes, like this:
var ConnectRoles = require('connect-roles');
var user = new ConnectRoles();
user.use('admin', function(req) {
return req.user && req.user.role === 'admin';
})
app.get('/admin', user.is('admin'), function(req, res, next) {
res.end();
})
Note that there are much better options for issuing & validating JWTs, like express-jwt, or using passport in conjunction with passort-jwt
I'm using express.js, passport with jwt strategy and of course jsonwebtoken for node.js.
So, currently, I've managed to implement a server-side logic, which enables users to login and returns the jwt token.
After that, when I do a get request with the corresponding token in the header, it correctly verifies the jwt token and display the info. The code is as follows:
var jwt = require('jsonwebtoken');
function createToken(user) {
return jwt.sign(user, 'shhhhh', {
issuer: "accounts.examplesoft.com"
});
}
var opts = {};
opts.secretOrKey = 'shhhhh';
opts.issuer = "accounts.examplesoft.com";
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
console.log(jwt_payload);
User.findById(jwt_payload.id, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
app.post('/jwt_login', function(req, res) {
User._loginJwt({
email: req.body.email,
password: req.body.password
}, function(err, user) {
if (err) res.json(err);
else res.json(createToken(user));
});
});
app.get('/jwt_test', passport.authenticate('jwt', {
session: false
}), function(req, res) {
res.json(true);
});
Now I'm trying to do a client-side page. I'm using angularjs and there are a lot of jwt libraries for angularjs or rather, client side in general. Now I have a series of questions:
First and foremost, is the server-side implement correctly (from what you can tell by the code above)?
Is it safe if I store the jwt token in localStorage (on client-side)?
Why are there so many libraries available for jwt client side? Isn't it enough to get the token and then call the requests with that token? What else could I do with that token on the client side?
Can't somebody just copy the jwt token from the localStorage and make requests as if they're logged in? Isn't that a security issue?
Thanks for your responses!
The server-side implementation looks fine, though the claims in the token could be expanded. Just always authenticate the token and you're good.
Yes. That's part of why JWT is useful. If the user alters the token, it will not match its signature and will fail authentication.
From what I recall, the client-side stuff is for when you pass data in the payload that is used on the client. You want to be able to authenticate the token on that side as well then, so your front-end doesn't do anything it shouldn't.
a. If you just have a RESTful API that validates requests with the token, you don't have to do anything with the JWT on the front-end besides sending it with requests.
Yes. That's why your token should include an expiration in its claims. Keep in mind, the only way that gets into LocalStorage is if they logged in to begin with.
See here for claims that can be in your token:
http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#rfc.section.4