I have a MEAN stack app with a REST like api. I have two user types: user and admin. To sign the user in and keep the session i use jsonwebtoken jwt like this (simplified):
const jwt = require("jsonwebtoken");
//example user, normally compare pass, find user in db and return user
let user = { username: user.username, userType: user.userType };
const token = jwt.sign({ data: user }, secret, {
expiresIn: 604800 // 1 week
});
To protect my express routes from I do this:
in this example it is a "get user" route, the admin is ofc allowed to get information about any given user. The "normal" user is only allowed to get information about him/her -self, why i compare the requested username to the username decoded form the token.
let decodeToken = function (token) {
let decoded;
try {
decoded = jwt.verify(token, secret);
} catch (e) {
console.log(e);
}
return decoded;
}
// Get one user - admin full access, user self-access
router.get('/getUser/:username', (req, res, next) => {
let username = req.params.username;
if (req.headers.authorization) {
let token = req.headers.authorization.replace(/^Bearer\s/, '');
decoded = decodeToken(token);
if (decoded.data.userType == 'admin') {
//do something admin only
} else if (decoded.data.username == username) {
//do something user (self) only
} else{
res.json({ success: false, msg: 'not authorized' });
}
} else {
res.json({ success: false, msg: 'You are not logged in.' });
}
})
So my question is, how secure is this? Could someone manipulate the session token to swap the username to someone else's username? or even change the userType from user to admin?
My guess is. Only if they know the "secret" but is that enough security? the secret is after all just like a plain text password stored in the code. What is best practice?
the secret is after all just like a plain text password stored in the code.
That's right. If secret is not kept secret, then an attacker can forge user objects and sign them using that secret and verify would not be able to tell the difference.
Putting secrets in code risks the secret leaking. It could leak via your code repository, because a misconfigured server serves JS source files as static files, or because an attacker finds a way to exploit a child_process call to echo source files on the server. Your users' security shouldn't depend on noone making these kinds of common mistakes.
What is best practice?
Use a key management system (KMS) instead of rolling your own or embedding secrets in code. Wikipedia says
A key management system (KMS), also known as a cryptographic key management system (CKMS), is an integrated approach for generating, distributing and managing cryptographic keys for devices and applications.
Often, how you do this depends on your hosting. For example, both Google Cloud and AWS provide key management services.
https://www.npmjs.com/browse/keyword/kms might help you find something suitable to your stack.
This is extremely secure, especially if sent over HTTPS - then an attacker has no idea what your request payload looks like.
The only real danger is making sure you keep the SECRET safe, don't save on public git repos, lockdown access to any box on which the secret is stored. Use an encoded secret.
There are also other ways to harden your server. Consider using npm's popular helmet module.
This is secure if your data packets are sent over HTTPS. If you want to add another layer of security what you can do is you can first encrypt the user details with the help of iron which uses 'aes-256-cbc' for encryption and then use that encrypted text and generate a token through JWT. In this way if a user somehow gain access to your token and go the website of JWT. He won't be able to recognize what's inside this token.
Again this is just to add an extra layer of security and it makes technically infeasible for a person to extract information because it's very time consuming yet add some extra security to our application.
Also make sure that all of your secrets(secret keys) are private.
To answer your initial question "Could someone manipulate the session token to swap the username to someone else's username? or even change the userType from user to admin?"
JWT tokens are encrypted from the server-side and sent to the client in the form of a response. With that token, there are two things to keep in mind:
Token is encrypted with a private key that only the server knows about
The token contains, what is known as, a signature which validates the contents of the JWT payload (e.g. userType etc.)
For more information regarding the first point, please refer to the following answer.
For a better visual representation of JWT tokens and signatures, take a look at the following URL - https://jwt.io/
With this in mind, you must make sure that:
Your secret key is never exposed at any point in time to external services/clients. Ideally this key is not even hard-coded in your codebase (ask, should you want me to elaborate on this).
You don't have any logical flaws in your endpoints.
From a design perspective, I would much rather segregate any endpoints related to admin-space into their own endpoints however at a quick glance, your code seems to be fine. :-)
On a side-note that may assist you, if you don't have much experience in Web Application security, I would recommend checking out some automated scanners such as Acunetix, Burp (hybrid) and so on. Whilst not perfect by any means, they are quite capable of detecting a good amount of behavioral vulnerabilities (as in, ones a malicious actor would normally exploit).
Some potential vulnerabilities in the code above:
Variable username comes from the url, but usertype is asserted from the token in the router action. An audit log for example could be corrupted if it took the userid from the username variable, because that is not authentic and can be anything sent by the user.
Replay is an issue. As the token is valid for one week, it is impossible to revoke admin rights for example until the token expires. (A malicious user could replay the previous admin token.)
Similarly, you cannot terminate (force logout) any session until tokens expire. This is a common issue in such stateless designs.
Null reference after token decode as pointed out by others. In node.js that is more like a weakness than a vulnerability I'd say, I think it's not exploitable.
The secret on the server is extremely valuable and can be used to impersonate any user. It is very hard to protect such a secret in systems with high security requirements.
So contrary to another answer, this is far from being 'extremely secure', but can be reasonably secure for many purposes.
Related
I am moving a project from React to Next.js and was wondering if the same authentication process is okay. Basically, the user enters their username and password and this is checked against database credentials via an API (Node.js/Express). So, I am not using Next.js internal api functionality, but a totally decoupled API from my Next.js project.
If the login credentials are correct, a JWT token is sent back to the client. I wanted to store that in local storage and then redirect the user. Any future HTTP requests will send the token in the header and check it is valid via the API. Is this okay to do? I ask because I see a lot of Next.js auth using cookies or sessions and don't know if that is the 'standard' approach which I should rather adopt.
My answer is purely based on my experiences and things I read. Feel free to correct it if I happened to be wrong.
So, my way is to store your token in HttpOnly cookie, and always use that cookie to authorize your requests to the Node API via Authorization header. I happen to also use Node.js API in my own project, so I know what's going on.
Following is an example of how I usually handle authentication with Next.js and Node.js API.
In order to ease up authentication problems, I'm using Next.js's built in getServerSideProps function in a page to build a new reusable higher order component that will take care of authentication. In this case, I will name it isLoggedIn.
// isLoggedIn.jsx
export default (GetServerSidePropsFunction) => async (ctx) => {
// 1. Check if there is a token in cookies. Let's assume that your JWT is stored in 'jwt'.
const token = ctx.req.cookies?.jwt || null;
// 2. Perform an authorized HTTP GET request to the private API to check if the user is genuine.
const { data } = await authenticate(...); // your code here...
// 3. If there is no user, or the user is not authenticated, then redirect to homepage.
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
// 4. Return your usual 'GetServerSideProps' function.
return await GetServerSidePropsFunction(ctx);
};
getServerSideProps will block rendering until the function has been resolved, so make sure your authentication is fast and does not waste much time.
You can use the higher order component like this. Let's call this one profile.jsx, for one's profile page.
// profile.jsx
export default isLoggedIn(async (ctx) => {
// In this component, do anything with the authorized user. Maybe getting his data?
const token = ctx.req.cookies.jwt;
const { data } = await getUserData(...); // don't forget to pass his token in 'Authorization' header.
return {
props: {
data,
},
},
});
This should be secure, as it is almost impossible to manipulate anything that's on server-side, unless one manages to find a way to breach into your back-end.
If you want to make a POST request, then I usually do it like this.
// profile.jsx
const handleEditProfile = async (e) => {
const apiResponse = await axios.post(API_URL, data, { withCredentials: true });
// do anything...
};
In a POST request, the HttpOnly cookie will also be sent to the server, because of the withCredentials parameter being set to true.
There is also an alternative way of using Next.js's serverless API to send the data to the server. Instead of making a POST request to the API, you'll make a POST request to the 'proxy' Next.js's serverless API, where it will perform another POST request to your API.
there is no standard approach. You should be worried about security. I read this blog post: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/
This is a long but an awesome blog post. everyhing I post here will be quoted from there:
If a JWT is stolen, then the thief can keep using the JWT. An API
that accepts JWTs does an independent verification without depending
on the JWT source so the API server has no way of knowing if this was
a stolen token! This is why JWTs have an expiry value. And these
values are kept short. Common practice is to keep it around 15
minutes.
When server sends you the token, you have to store the JWT on the client persistently.
Doing so you make your app vulnerable to CSRF & XSS attacks, by
malicious forms or scripts to use or steal your token. We need to save our JWT token somewhere so that we can forward it to our API as a header. You might be tempted to persist it in localstorage; don’t do it! This is prone to XSS attacks.
What about saving it in a cookie?
Creating cookies on the client to save the JWT will also be prone to XSS. If it can be read on the client from Javascript outside of
your app - it can be stolen. You might think an HttpOnly cookie
(created by the server instead of the client) will help, but cookies
are vulnerable to CSRF attacks. It is important to note that HttpOnly
and sensible CORS policies cannot prevent CSRF form-submit attacks and
using cookies requires a proper CSRF mitigation strategy.
Note that a SameSite cookie will make Cookie based approaches safe
from CSRF attacks. It might not be a solution if your Auth and API
servers are hosted on different domains, but it should work really
well otherwise!
Where do we save it then?
The OWASP JWT Cheatsheet and OWASP ASVS (Application Security
Verification Standard) prescribe guidelines for handling and storing
tokens.
The sections that are relevant to this are the Token Storage on Client
Side and Token Sidejacking issues in the JWT Cheatsheet, and chapters
3 (Session Management) and 8 (Data Protection) of ASVS.
From the Cheatsheet, "Issue: Token Storage on the Client Side":
Automatically sent by the browser (Cookie storage).
Retrieved even if the browser is restarted (Use of browser localStorage container).
Retrieved in case of XSS issue (Cookie accessible to JavaScript code or Token stored in browser local/session storage).
"How to Prevent:"
Store the token using the browser sessionStorage container.
Add it as a Bearer HTTP Authentication header with JavaScript when calling services.
Add fingerprint information to the token.
By storing the token in browser sessionStorage container it exposes
the token to being stolen through a XSS attack. However, fingerprints
added to the token prevent reuse of the stolen token by the attacker
on their machine. To close a maximum of exploitation surfaces for an
attacker, add a browser Content Security Policy to harden the
execution context.
"FingerPrint"
Where a fingerprint is the implementation of the following guidelines
from the Token Sidejacking issue: This attack occurs when a token has been intercepted/stolen by an attacker and they use it to gain access to the system using targeted user identity.
"How to Prevent":
A way to prevent it is to add a "user context" in the token. A user context will be composed of the following information:
A random string will be generated during the authentication phase. It will be sent to the client as a hardened cookie (flags: HttpOnly +
Secure + SameSite + cookie prefixes).
A SHA256 hash of the random string will be stored in the token (instead of the raw value) in order to prevent any XSS issues allowing
the attacker to read the random string value and setting the expected
cookie.
I'm currently trying to build a REST API using express, node, and MongoDB. Now for authentication, I'm using JWT.
Here is the code for checking JWT token
const token = req.headers['authorization'];
if (token){
const tokens = token.split(' ');
const key = tokens[1];
jwt.verify(key, config.jwtKey, (err, authData) => {
if (err){
res.status(403).json({
success: false,
message: "Authentication2 failed"
});
}
// User authenticated
// Do something
next();
});
} else {
res.status(403).json({
success: false,
message: "Authentication failed"
});
}
Now, this code is working perfectly.
For making the JWT, here is the code
........
........
const token = jwt.sign(
{
email: user[0]._email,
userId: user[0]._id
}, config.jwtKey,{ expiresIn: "1d" });
........
........
Now my question is should I also verify the user by checking the existence of the user's information in the database to make it more secure?
For example, searching the email and userId to my database.
I'm using node, express, MongoDB, Mongoose, and JWT for this project.
It's an old question but I want to leave an answer:
Yes! Everytime your client make an api request, backend should verify both the validity of the token and the presence somewhere in your backend (for example a db table).
Consider always the most dangerous scenario: bank account
What happens if someone steal your device?
You should be able to invalidate the token from another device and change the password.
The advantage of using a token is that the server can
verify it quickly without calling out to an external data store like MongoDB.
But if you're going to add a business login to your API authentication like a blacklist/whitelist of revoked tokens then you have to use a store to verify the token and user details, (will be slower than not doing a remote call for each token but you have to do it with low latency).
For low latency you have to use DB like Redis, Dynamodb would probably be fine and more secure without major latency between your DB and your API server.
Is not required to verify the signature with DB, and you can settle for JWT algorithm
Verifying (the signature of) the token using the selected algorithm is enough to ensure that this user exists (or existed) on the system because it was the system who generated the token in the first place.
But there are cases when that's not enough, for example, blacklists as mentioned in Roy G's answer, or if the users' claims have been changed or completely deleted from the system but they are still using an old token (not expired yet), they could still have access to the system, so checking against DB would prevent that access.
Setting a small expiry date in combination with refresh tokens is generally a good practice to prevent those kinds of leaks.
I am using jwt plugin and strategy in hapijs.
I am able to create jwt token while login user and authenticate other API using the same token through 'jwt' strategy.
I am setting the token in request.state.USER_SESSION as a cookie where USER_SESSION is a token name. Also, I am not saving these token in the database.
But how can I destroy jwt token at the time of logout?
Please suggest a way.
The JWT is stored on browser, so remove the token deleting the cookie at client side
If you need also to invalidate the token from server side before its expiration time, for example account deleted/blocked/suspended, password changed, permissions changed, user logged out by admin, take a look at Invalidating JSON Web Tokens for some commons techniques like creating a blacklist or rotating tokens
You cannot manually expire a token after it has been created. Thus, you cannot log out with JWT on the server-side as you do with sessions.
JWT is stateless, meaning that you should store everything you need in the payload and skip performing a DB query on every request. But if you plan to have a strict log out functionality, that cannot wait for the token auto-expiration, even though you have cleaned the token from the client-side, then you might need to neglect the stateless logic and do some queries. so what's a solution?
Set a reasonable expiration time on tokens
Delete the stored token from client-side upon log out
Query provided token against The Blacklist on every authorized request
Blacklist
“Blacklist” of all the tokens that are valid no more and have not expired yet. You can use a DB that has a TTL option on documents which would be set to the amount of time left until the token is expired.
Redis
Redis is a good option for blacklist, which will allow fast in-memory access to the list. Then, in the middleware of some kind that runs on every authorized request, you should check if the provided token is in The Blacklist. If it is you should throw an unauthorized error. And if it is not, let it go and the JWT verification will handle it and identify if it is expired or still active.
For more information, see How to log out when using JWT. by Arpy Vanyan(credit and reference)
On Logout from the Client Side, the easiest way is to remove the token from the storage of browser.
But, What if you want to destroy the token on the Node server -
The problem with JWT package is that it doesn't provide any method or way to destroy the token.
So in order to destroy the token on the serverside you may use jwt-redis package instead of JWT
This library (jwt-redis) completely repeats the entire functionality of the library jsonwebtoken, with one important addition. Jwt-redis allows you to store the token label in redis to verify validity. The absence of a token label in redis makes the token not valid. To destroy the token in jwt-redis, there is a destroy method
it works in this way :
1) Install jwt-redis from npm
2) To Create -
var redis = require('redis');
var JWTR = require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);
jwtr.sign(payload, secret)
.then((token)=>{
// your code
})
.catch((error)=>{
// error handling
});
3) To verify -
jwtr.verify(token, secret);
4) To Destroy -
jwtr.destroy(token)
Note : you can provide expiresIn during signin of token in the same as it is provided in JWT.
If you just want to remove the token, it will be simple as removing it from the front end application, In you case clear the cookies that stores the token
On the other hand if you mean to invalidate the token, there is couple of ways to do it, below are some ways
(1) If all the token ever generated is stored in backend, It will be just simple as clearing that storage, if tokens have been mapped to users you can just clear tokens for a particular user.
(2) You can add a date field like "invalidate_before" along with user which should be updated at a event of changing password, logout from all devices etc.
Simply update the invalidate_before to currentTime() on such events.
Every time a new token is created, add the created time in token payload,
to validate the token on incoming request just check if the created time in payload is greater than invalidate_before time for that user in db
(3) When you create a new user, create a secret for just that user, then you can sign every user token with that specific secret, and just like in (2) events like changing password, logout from all devices etc, Should create a new secret.
This way also you can invalidate by checking the token signature.
overhead with (2) and (3) is that, validation will be a 2 step process and it involves db reading
EDIT: For (3) you may use a salt instead (final secret will be common secret + salt for particular user), So that you hava a way to invalidate either a single user's token by changing salt or the all user's token by changing common secret
You can add "issue time" to token and maintain "last logout time" for each user on the server. When you check token validity, also check "issue time" be after "last logout time".
While other answers provide detailed solutions for various setups, this might help someone who is just looking for a general answer.
There are three general options, pick one or more:
On the client side, delete the cookie from the browser using javascript.
On the server side, set the cookie value to an empty string or something useless (for example "deleted"), and set the cookie expiration time to a time in the past.
On the server side, update the refreshtoken stored in your database. Use this option to log out the user from all devices where they are logged in (their refreshtokens will become invalid and they have to log in again).
OK so I tried something that I wanna share I think it's a really easy and effective method so basically instead of destroying your token or blacklist it we can simply append a random value to it in the middle in a random index or even in the end of it like a random number (or a random hashed number) to make it harder for anyone to reverse it and obtain the previously valid token, Doing so makes this token invalid so the user won't go anywhere and from the front-end you can redirect the user to login again (or even from the back-end however I prefer if the front-end did it) so the user logs out they get redirected to the login page and it's all good, Here's my code. first of all I have an auth middleware that if the token(password & username) is OK it appends the token to req.token so whenever I call this middleware the user's token will be save to req.token
router.post('/logout', auth, async(req, res) => {
try{
let randomNumberToAppend = toString(Math.floor((Math.random() * 1000) + 1));
let randomIndex = Math.floor((Math.random() * 10) + 1);
let hashedRandomNumberToAppend = await bcrypt.hash(randomNumberToAppend, 10);
// now just concat the hashed random number to the end of the token
req.token = req.token + hashedRandomNumberToAppend;
return res.status(200).json('logout');
}catch(err){
return res.status(500).json(err.message);
}
});
right now it will concat the the hashed random number to the end of the token which means it's no longer valid so the user will have to login again as they will be redirected to the login page
Token-Based Authentication: JWT? Check. GET /items/:id... How?
This is a small question, but probably a big answer, as I'm a bit new to this...
Once I have provided a client a JWT -- and they wish to obtain a resource -- what does the logic-flow of verifying Client look like?
In other words, I have a JWT payload such as...
{
...
"sub": user.id
...
}
... and Client needs to access item 998 at /api/items/:id...
My current approach looks something like the following.
// ItemsController.lang | 'GET /api/items/:id'
var userId = jwt.decode(token).sub;
var isValid = checkUserIdInDatabase(userId);
var secureResource = ORM.findOne({ user: userId, id: request.itemId });
response.send(secureResource);
Along with this, when Client signs up/in, I provide them a response which looks like this...
{ user: { id: 998, email: 'no#username.com', preferences: [...] }, jwt: token }
Should I ever be sending id & email if I'm issuing a JWT?
Given that I should have middleware to check if my jwt.sub's [userId] value exists in the database, should I use this userId as part of my query, or should Client be sending Server the userId as request.body.userId since it obtains it upon sign in/up?
Is it a no-no to assign a userId to payload.sub?
Do I need to generate a new JWT with a new lifespan upon every request and send it to the client?
What are all my security blunders, what is best practice, and what would you do?
Every tutorial I look at shows a nice & clean high-level flow stating [simply] that 'if the JWT is verified, the resource is sent to the client'.
Can you please provide me with some direction on how all this token-based authentication stuff is supposed to work at the low-level -- namely, in order to request secure resources?
PreThanks,
Cody
This is solely my point of view and i am no expert, here it goes:
1) I think you should not send user ID in your response as there is not a lot of scenarios where i see that would be necessary for client to know its user id, you easily assign this to your token payload and use a middle ware like express-jwt
to do the decoding and giving the user id for you.
2) Do not rely on sensitive data sent by client, if you use express-jwt then it would assign user id with every request object eg: req.user.userId
3) you can assign user id and other small user session data in your payload, your client would require your secret key and to be able to decode that information , always have a strong secret key.
4) I think you should have token life span expire after a day or even less, there are scenarios where you may need to go longer i think 1 week should be maximum amount of time you should have your token life span. now you can always refresh your token and have your application check after some interval of time if its token is valid and request for refresh or new one after expiration .
5) may be these:
Have a strong secret key.
Use cookies to store secret key for web applications
Keep your token life span short as possible.
Use HTTPS protocol for APIs.
Write a middle-ware or use already existing middle-wares for user authentication. eg: express-jwt
jwt tutorial tutorial2 ,using storm-path and using passport
Hopefully this helps.
I've looked through the source code and tests, but don't see a way to revoke a token. I need to cover scenarios where I disable access for a user and need that occur immediately.
Given that the token is stored in keyChain.bin, it might be possible to deserialize the collections, detokenize all tokens, remove the desired on, the serialize the collection again. This sounds elaborate. Are there any other methods I could use?
Update
Potentially I can keep a separate list of user ids and the token that they have been issued, then match the token with the keyChain collection.
Update 2
After playing with the keyChain file, things are little more confusing. After creating a new user, I issue a token:
var newServiceIdentity = siteSecurityManager.CreateServiceUser(user.UserId, user.Password)
.GetServiceIdentity();
string token = _tokenizer.Tokenize(newServiceIdentity, this.Context);
newServiceIdentity.Token = token;
siteSecurityManager.RegisterToken(newServiceIdentity);
var fileStore = new FileSystemTokenKeyStore(_rootPathProvider);
var allKeys = fileStore.Retrieve() as Dictionary<DateTime, byte[]>;
return new { Token = token };
By default, Nancy will store each token in a binary file so that should your server need to be bounced, the user sessions will survive. With a different browser session, I connect with the new users credentials and gain access to the site. When I add another user, I would expect that the allKeys count would incremented to reflect my admin session as well as the new user that is connected with a different browser. I see a count of one, and the key matches the admin token.
My login method does indeed issue a token for each user that connects with correct credentials. That logic is:
var userServiceIdentity = ServiceIdentityMapper.ValidateUser(user.UserId, user.Password);
if (userServiceIdentity == null)
{
return HttpStatusCode.Unauthorized;
}
else
{
string token = _tokenizer.Tokenize(userServiceIdentity, this.Context);
return new { Token = token };
}
I store the token and return it with each Ajax call. The implication here is that I do have the tokens recorded, otherwise authentication would fail. But if the keyChain.bin is not updated, then I can't pursue the idea of registering the token and user in a separate store, then purging that token to revoke access.
As explained to me by Jeff, the code's author, the keyChain.bin stores only the key that is used to generate the token. This is so that the relevant information is only stored on the client, and a simple comparison of the client token is used to avoid querying a back-end source. Jeff's complete explanation is here
A possible solution would be to keep a separate list of black listed tokens / users. Perhaps this is best dictated by business practices. There are indeed times where you should lock a user out of immediately. This could be accomplished by issuing a purge for all tokens, and force a login for all legitimate users. A minor inconvenience for some scenarios, and unacceptable for others.