Nodejs - Is it necessary to decode an Authentication Token? - node.js

I am using a combination of Node.js and Passportjs on the server-side, and Emberjs on the client side for an app. My current Authentication strategy is to use Passport-Local to authenticate users with normal email/password combinations as standard, and then hook in a session creation mechanism to generate an Authentication Token, which is saved into a separate table, and passed back to the user for use in any further protected routes. (passed in the REST header).
Creation of the token is fine, i'm doing that without issue, however i'm struggling to work out if I need an extra step.
Currently, I generate the token with node-jwt-simple by using a random node-uuid pass as the payload, and the users UID (another node-uuid) as the secret. I then save this to a $.cookie on the clientside, and to a table on the serverside, along with a creation date.
Obviously, one of the steps in node-jwt-simple is to encode the token. There is also a decode function provided. My question is, do I need to decode the token into something when I am doing my auth checking, or is simply checking the user's session cookie (REST header) for a match against the token in the database sufficient? I wouldn't want to go to all the effort of having generated a token, only to then miss an important step, but i'm not seeing how I could decode it into anything that would provide any additional useful security.
UPDATE:
I think I worked this out last night:
The solution seems to be to use the User's UID as the payload for JWT, with a static string as the secret (taken from something like a server environment variable or similar), and then only store the encoded token in the database. Pass the token back to the client for re-auth, then when the client attempts to access a protected route, they must pass their UID along with the encoded token to the server, which is then decoded, and the decoded payload compared to the UID that has been passed. If they match, the auth is successful, otherwise the token is destroyed and the user has to log in again.
By doing this, it makes the store of tokens effectively useless without knowing either the Secret key, or having the User's UID, but makes the auth process more secure.

If you don't validate the token, you could as well create some other random data to use as a session cookie, as long as it is unique and cannot be guessed by clients.
But as you already made a lot of effort, you could encode something useful in the token which tells you how long it is valid, e.g. an exp field, so you don't have to read from the database.
I'm not sure if I fully understand your JWT, but the problem I see is that you need information to decode the token which is probably not at your hand. So you have to do a search in your database.
I think it would be sufficient to use some random session key, e.g. following function:
var crypto = require('crypto');
/**
* Create random bytes and encode base64url.
* #param {int} [lengthInBytes=40] the size of the raw token in bytes
* (will be longer since base64url-encoded)
* #param {function} callback node-style callback-function;
* data-parameter is a string w/ a shortened (no trailing ==)
* base64url-encoded string of the generated bytes.
*/
exports.createRandomToken = function createRandomToken(lengthInBytes, callback) {
if (typeof lengthInBytes === 'function') {
callback = lengthInBytes;
lengthInBytes = 40;
}
crypto.randomBytes(lengthInBytes, function (ex, buf) {
if (ex) {
callback(ex);
return;
}
callback(null, buf.toString('base64')
.replace(/\//g, '_')
.replace(/\+/g, '-')
.replace(/=/g, ''));
});
};

Set expire time of the token as small as possible(5 min, 30 min, nor months neither years).
Use refresh token to get a new token and update refresh token every time you update old token (and when user is logged in, no doubt)
Do not store passwords, credit card numbers and any confidential informations in the token ( I'm shure, you know it :) )
Store all necessary information for checking privileges ( or checking ip, for example ). It is good for REST API and horizontal scaling.

Related

How to create proper user identification using hashed values

Completely new to backend, sessions and cryptography. So I'm asking here what would be a proper way to identify users for a web application.
Here's the story:
I'm developing an app for Shopify e-commerce platform. When merchant initially accesses the app, Shopify sends hmac validation to the app. When Shopify authorization hmac is validated app stores 'shopname' cookie and sends app page back as response.
The thing is when merchant follows other app routes there's no any methods to validate that the merchant is actually who he/she is. The only information app has at these routes is 'shopname' cookie. I've came up with some random hash values to identify merchants. What I do is create hmac (as 'app-token' cookie) from random values and store them and date of creation to DB under 'shopname' value.
When a route gets request app reads 'shopname' and 'app-token' cookies and asks DB for the stored values. Then checks if no more than 1 day passed since 'app-token' creation, creates hmac from these values and validates 'app-token' cookie on equality. If more than 1 day passed merchant is redirected to Shopify hmac authorization route and new 'app-token' hmac cookie is being generated and stored to DB (this is the only place where it is being generated).
This is what 'app-token' looks like at the moment:
var random_num = Math.random().toString();
var auth_hash = crypto.createHash('md5').update(random_num).digest('base64');
var auth_hmac_random_num = Math.random().toString();
var auth_hmac = crypto.createHmac('sha256',auth_hash).update(auth_hmac_random_num).digest('hex');
var auth_hmac_concant_random_num = Math.random().toString();
var auth_hmac_concat = crypto.createHash('md5').update(auth_hmac_concant_random_num).digest('hex');
var auth_hmac = auth_hmac_concat + auth_hmac;
auth_hash, auth_hmac_random_num, auth_hmac_concat are being stored to DB under 'shopname' value as JSON.
On request verification auth_hmac_concat is removed from 'app-token' cookie and hmac is created from auth_hash and auth_hmac_random_num and being validated to 'app-token' with crypto.timingSafeEqual(hmac, app_token).
Again, I'm completely new to backend, and I might be overcomlicating or oversimplifying things. The question is what would be a proper way to identify merchants in described curcumstances? Hope I was clear enough.
If you create a session when the shop authenticates, you are in business. There is really no need to go to your extreme lengths of fashioning your own next-level authentication. If a session exists, the shop gets access to your App. No session, authenticate them. Pretty much the same pattern as any and all worthy web apps for the past decade right? Why complicate things?
The one case where you can be clever is when your App has a callback you made, and the session has expired. In this case, you need to gracefully re-auth and get the App back to where it was, to be nice to the merchant. Not many Apps do this well, but you can.

How to implement authentication in Next.js

I am new to Next.js and I am struggling with the authentication system using jwt token. I want to know what is the best / standard way to store the jwt token and routing with the authentication system. I have been trying different approaches, from different tutorials/articles, but do not quite understand it. Here are what I have tried.
When the user login, it sends username/password to a separated API server (ex. new project that handles backend stuff), the server will respond with the access-token, then in Next.js project, I set the cookie with that received token. In Next.js project, protected routes will be wrapped with a withAuth hoc, which will check for the token in a cookie. The problem with this approach is that it is vulnerable to XSS because the cookie has no httpOnly flag.
This is similar to 1.) but using localStorage, the problem is access-token could not be sent to the server on the first request. (This one I'm not sure, but in my understanding, in every HTTP request, I must stick my access-token manually, so what about requests that I have no control over? ex. first request or using <a> tag).
I wrote authentication backend inside Next.js server (custom express server). When the user login, the server will validate it and then set an httpOnly cookie. Then the problem is, with client-side routing (go to URL using Next.js Router), it could not check for token. For example, if a page is wrapped with withAuth hoc, but it cannot access the token inside cookies with javascript.
And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage, then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to the server in every client-side page change?
Since we are on quarantine I have enough time to answer this question. It will be a long answer.
Next.js uses the App component to initialize the pages. _app page is responsible for rendering our pages. We authenticate users on _app.js because anything that we return from getInitialProps can be accessed by all of the other pages. We authenticate user here, authentication decision will be passed to pages, from pages to header, so each page can decide if the user is authenticated or not. (Note that it could be done with redux without prop drilling but it would make the answer more complex)
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {};
const user = process.browser
? await auth0.clientAuth()
: await auth0.serverAuth(ctx.req); // I explain down below
//this will be sent to all the components
const auth = { user, isAuthenticated: !!user };
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps, auth };
}
render() {
const { Component, pageProps, auth } = this.props;
return <Component {...pageProps} auth={auth} />;
}
}
If we are on the browser and need to check if a user is authenticated, we just retrieve the cookie from the browser, which is easy. But we always have to verify the token. It is the same process used by browser and server. I will explain down below. But if we are on the server. we have no access to the cookies in the browser. But we can read from the "req" object because cookies are attached to the req.header.cookie. this is how we access to cookies on the server.
async serverAuth(req) {
// console.log(req.headers.cookie) to check
if (req.headers.cookie) {
const token = getCookieFromReq(req, "jwt");
const verifiedToken = await this.verifyToken(token);
return verifiedToken;
}
return undefined;
}
here is getCookieFromReq(). remember we have to think functional.
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
Once we get the cookie, we have to decode it, extract the expiration time to see if it is valid or not. this part is easy. Another thing we have to check is if the signature of the jwt is valid. Symmetric or asymmetric algorithms are used to sign the jwt. You have to use private keys to validate the signature of symmetric algorithms. RS256 is the default asymmetric algorithms for APIs. Servers that use RS256, provide you with a link to get jwt to use the keys to validate the signature. You can either use [jwks-rsa][1] or you can do on your own. You have to create a certificate and then verify if the token is valid.
Assume that our user authenticated now. You said, "And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage,". We use protected routes to give access only to the authorized users. In order to access those routes, users have to show their jwt tokens and express.js uses middlewares to check if the user's token is valid. Since you have seen a lot of examples, I will skip this part.
"then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to a server in every client-side page changing?"
with verifying token process we are 100% sure if the token is valid or not. When a client asks the server to access some secret data, the client has to send the token to the server. Imagine when you mount the component, component asks the server to get some data from the protected routes. The server will extract the req object, take the jwt and use it to fetch data from the protected routes. Implementation of the fetching data for browser and server are different. And if the browser makes a request, it just needs the relative path but the server needs an absolute path. As you should know fetching data is done getInitialProps() of the component and this function executed on both client and server. here is how you should implement it. I just attached the getInitialProps() part.
MyComponent.getInitialProps = async (ctx) => {
const another = await getSecretData(ctx.req);
//reuslt of fetching data is passed to component as props
return { superValue: another };
};
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
const setAuthHeader = (req) => {
const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");
if (token) {
return {
headers: { authorization: `Bearer ${token}` },
};
}
return undefined;
};
export const getSecretData = async (req) => {
const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";
return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
};
[1]: https://www.npmjs.com/package/jwks-rsa
With the introduction of Next.JS v8, there are examples placed in the NextJS example page. The basic idea to follow is:
JWT
Using cookies to store the token (you may choose to further encrypt it or not)
Sending the cookies as authorization headers
OAuth
Using a third-party authentication service such as OAuth2.0
Using Passport
This question might need an updated answer, now middlewares are there in Next.js 12 (october 2021): https://nextjs.org/docs/middleware
I am drafting a comprehensive answer to explain auth in Next.js more deeply, you can follow the progress there on GitHub
Here I'll try to propose a summary for Next.js, using middlewares.
Verifying the token after auth and redirecting accordingly
Most of the answer from #Yilmaz from april 2020 is still relevant. However, previously, we had to use getInitialProps in _app to process the request OR a custom server.
This is no longer the case.. Using a middleware let's you achieve a similar purpose, with cleaner code. Because middleware are specifically designed for such use cases.
Here, I suppose you get a JWT access token using an asymetrical algorithm like RS256, exactly like in this previous answer.
Here is a possible implementation:
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
const removeCookie = (res: NextResponse, cookieName: string) => {
res.headers.append("Set-Cookie", `${cookieName}=; Max-Age=-1; Path=/`);
return res;
};
export default async function middleware(
req: NextRequest,
ev: NextFetchEvent
) {
const { pathname } = req.nextUrl;
const isPublic = isPublicRoute(pathname);
if (isPublic) {
return NextResponse.next();
}
const accessToken = req.cookies[TOKEN_PATH];
if (!accessToken) {
return NextResponse.redirect(LOGIN_HREF);
}
const isValidToken = await checkAccessToken(accessToken);
if (!isValidToken) {
let res = NextResponse.redirect(LOGIN_HREF);
res = removeCookie(res, TOKEN_PATH);
return res;
}
return NextResponse.next();
}
How to verify the token
In my example, the checkAccessToken should verify the token (not decode, verify the signature).
This is where things are the most complicated imo.
When using the RSA256 algorithm
You also get a PUBLIC certificate (in addition to the SECRET key that must be... kept secret). Eventhough you do the check in the middleware, which is private and server-only code, that's good news because it means you could even use it in the browser, in theory.
So, you can either fetch the token validation endpoint provided by your auth server, or verify the token yourself.
Fetching is not the recommended option because it might break Vercel/Next edge capabilities and add latency, according to the documentation.
I must admit that I did not succeed to verify the token yet using Next.js :) I'll update this answer if I manage to have a code sample that works.
When using a symmetrical encryption
You have only a PRIVATE secret passphrase. It means that the decoding have to happen server-side (good news, you are writing a middleware).
Login/logout
This doesn't change with middlewares. You store your access token as an httpOnly cookie. When logging out, you unset this cookie.
Managing those Set-Cookies headers are the responsibility of your auth server.
This is a basic workflow but it should work. You can then add a refresh token in the mix with a similar approach.
About token revokation
If you verify the token in your middleware, there is no immediate revokation mechanism for the access token. Because there is no call to a database.
Therefore, in this scenario, you'd want to opt-in for short lived access token (eg 5 minutes) coupled with a refresh token. You can revoke the refresh token, so basically revoking works but takes a few minutes.
If a 3rd party server verifies the token: then it could check for blacklisted tokens.
Caveats
Also, some piece of advice: most articles, tutorials etc. online are focused on server-to-server communication. Or client-to-API. They completely suck when it comes to check authentication before accessing web pages.
For instance, setting the Authorization header is not possible in the browser. It works only when communicating with an API. Cookies are mandatory for web pages.
Even then, if this API is meant to be called from a browser, it should preferably accept a cookie.
When discussing with experts on the field, you need to always clarify the Next.js use case.
Open questions: about session-based authentication
Some frameworks seem to prefer relying on the database. They store a hashed token in the db, which acts as a session. If you want to check auth, you need a server that will check the user's token against the stored token (= checking that there is an active session with this token).
I am thinking of Meteor for instance.
I couldn't find the name of this mechanism and its actual relation to JWT however. Are they simply variations of the JWT approach?
Next.js official authentication doc is not showing middlewares at the time of writing, but instead use getServerSideProps. I really don't like this pattern.
It uses a kind of session system but I am not clear about the internals of it, I am not even sure of the name (is that session-based auth?).
Vercel edge handles examples shows how to secure an API route, but not a page (at the time of writing)

CSRF Implementation and Stripe API

Is the following a secure implementation of CSRF token verification? Specifically, I want to call this Stripe API endpoint:
https://connect.stripe.com/express/oauth/authorize?redirect_uri=https://example.com&client_id=ca_11111&state={STATE_VALUE}
and the Stripe docs say
To prevent CSRF attacks, add the state parameter, passing along a unique token as the value. We’ll include the state you gave us when we redirect the user back to your site.
I am thinking of implementing like this:
- browser generates a random token and stores it in a cookie (or local storage)
- browser calls the above endpoint
- stripe redirects to my application at https://example.com
- when I get the response back, I check the contents of the state parameter and compare it to the value stored in local storage, or in my cookie.
Is this a secure/correct implementation of CSRF? Or do I need to somehow involve a backend service?
(Stripe docs here: https://stripe.com/docs/connect/express-accounts)
Notice that in the Rocket Ride example written by Stripe themselves in NodeJS, the state is just calculated on the server as a random string and added to the session of the user. When Stripe responds at the redirect_uri they just compare the state from the user's session with the state included in the response. The following two code snippets are taken from the following - https://github.com/stripe/stripe-connect-rocketrides/blob/master/server/routes/pilots/stripe.js
/**
* GET /pilots/stripe/authorize
*
* Redirect to Stripe to set up payments.
*/
router.get('/authorize', pilotRequired, (req, res) => {
// Generate a random string as `state` to protect from CSRF and include it in the session
req.session.state = Math.random()
.toString(36)
.slice(2);
// more stuff below
});
And for the check -
/**
* GET /pilots/stripe/token
*
* Connect the new Stripe account to the platform account.
*/
router.get('/token', pilotRequired, async (req, res, next) => {
// Check the `state` we got back equals the one we generated before proceeding (to protect from CSRF)
if (req.session.state != req.query.state) {
return res.redirect('/pilots/signup');
}
// more stuff below
});

security - access token best practises

I'm currently working on a simple API that's going to be the backend of my app. I'm designing my data structures right now and have some questions about security best practises.
Project background
The project is written in node.js
Since this is going to be an API (with a couple of protected methods) I think it's best to use an access token based authentication system. (user logs in and retrieves their token, which can then be used to access the protected methods)
The data is structured in such a way that every user has both a unique username and a unique token. For the purpose of this question (and my project) let's assume that this token never ever changes. However it is stored in plain text in the database.
To make sure that people with access to the database don't have direct access to the API by just copying this token I encode the token before I send it out. This way the actual authentication token is not the same as the token stored in the database but the server can decode the auth_token to recreate the token and blah blah..
However people with access to the database can just encode the token themselves... And I'm trying to find out if there's a workaround to this problem.
My question:
I'm trying to find out if there's an even safer way to do this. So please give me all of your ideas and best practise rules etc.
BTW, I considered using a salted hash as encryption but this creates the exact same problem, to match the stored token with the actual access token you'll need to also store the salt, since you can't revert an encryption like that. And storing the salt gives people with access to the database a way to create the actual access token...
PS:
I'm trying to create the entire authentication process myself, so please don't suggest things like passport.js. I mean there are great helper tools out there, I just want to do this all manually.
[Update] project background
Down here I'll attempt to sketch some more background to this project.
It is indeed a project for high school informatics. (as mentioned below)
The idea is simple: a TODO app, you might have heard of node-todo, that's basically it.
This is a group assignment and the levels of expertise among the group members highly varies. (Some have only ever done HTML and (some) CSS, others have done some JS) I've taken the responsibility of creating the backside of the application (the API to interact with the database) since none of the other group members know how. Since the other group members are not all as experienced I have no clue what the front end is going to look like. (My best guess it'll start off a web app)
So basically I want to create a backend that works for every client, and it should be independent. That is, the client is not going to be hosted on the same server (most likely the test are going to be locally only).
API-oriented. Modern applications compose and expose APIs everywhere. They build on open web techniques and use REST, XML and JSON to make it easy for all types of devices and clients to easily consume data. Any visible service or piece of data has a "headless" API counterpart, so that alternative views can be provided. In many cases, the APIs that a modern application exposes form the basis for a public, 3rd party developer community that enables mash-ups, plug-ins and innovation on a core set of data and services that drive a company's business
~ zdnet.com
If you do not mind saving state on your server, you could use a hashtable to save tokens:
Once the user logs in, you generate a random hash. This hash is used as key in the table. You can verify incoming requests by validating that the used token is indeed a key in your table.
To be able to identify which user is logged in, you should add a user identifier (like user email) to the saved object. You might also want to add the used IP-address and an expiration date to this object to further improve security.
By using this technique, you do not need to save tokens in your database :) Only hashed passwords
(You might want to clean your hashtable now and again to delete expired entrees)
Good luck on your project and say hi to Ramon for me
I usually just use jwt with it's own salting process. I don't store the token at all in my db, but just use it to store the user's information and set an expiry of 8 hours or so.. This way, you don't need to store tokens in the db, you just decode the token on each request and access the userID stored within its payload. The only downside is if someone steals someone elses token, but you can add in an IP address to the payload and make sure the clients ip is the same as the one stored in the token.
I'm not using passport, but I am using bcrypt for password encrption and JWT for my tokens. Sorry if this isn't what your looking for. Cheers.
Here is my auth library:
(function() {
var bcrypt = require('bcrypt-nodejs'),
jwt = require('jsonwebtoken'),
helpers = require('../lib/helpers.lib.js');
var JWT_SECRET = 'XXXXX';
module.exports = function() {
var self = this;
//Returns true if valid token given, false otherwise
self.validToken = function(token) {
if (token) {
try {
var decoded = jwt.verify(token, JWT_SECRET);
return true;
} catch (err) {
// console.log(err)
return false;
}
} else {
return false;
}
};
self.decodeToken = function(token) {
if (!token) {
return null;
}
try {
var decoded = jwt.verify(token, JWT_SECRET);
return decoded;
} catch (err) {
// console.log(err)
if (err.name == "TokenExpiredError") {
return 'expired';
}
return null;
}
};
self.createToken = function(payload, expires) {
options = { "expiresIn": ((expires) ? '2d' : '14d') };
return jwt.sign(payload, JWT_SECRET, options);
};
self.hashPassword = function(password) {
return bcrypt.hashSync(password)
};
self.comparePassword = function(plainTextPassword, hashedPassword) {
return bcrypt.compareSync(plainTextPassword, hashedPassword);
};
return self;
};
}());
multiple things come in mind:
- have you looked at Firebase?
- what about things like two factor authentication (e.g. speakeasy or authy)?
You do not give too many details on the project background, I understood from mr. Moorlag that it has to do with high school informatics. So in case you want to make a proof-of-concept (e.g. for a Profielwerkstuk (± high school thesis in Dutch schools)) you would have different requirements than 'just implementing an app' that you would want to publish in the Play / App Store of course.
Some minor students of mine did a workshop two weeks ago about Firebase and then you do not need to worry about such a things, works really great (note: Firebase is NoSQL so forget normalization ;)). If you want to do research on it I would find out the details about two factor authentication and also have a look at TLS / HTTPS (Let's Encrypt?! yay!).

Which data put in jwt-tokens (node.js + mongoose)?

Sorry for bad English.
To generate JWT-token i'm using
jwt = require('jsonwebtoken');
/* some code */
//function to create jwt-token
function createToken(user) {
return jwt.sign(user, config.secret, { expiresIn: 60*60*5 });
}
/* some code */
//after registration/authorization (if success) createToken token function is calling
createToken(user);
I would like to learn the best practices, what data must be passed to a function to create the token.
For example, can this be as:
login (John) and id( ObjectId("5821d94dbb021a1360582da3") when using MongoDb)?
And here, I think, will be relevant question:
If I store in token some information that allows initialize user, I can pull its data from the database. Is this correct, initialize the user from authorization header from JWT? For initialization I'm using express-jwt, which, if successful, sets req.user?
Thanks.
you can put the generated token in browser local storage, if you are using browser. You can also store session values in the token itself, those values will be decoded on the server side by nodejs.

Resources