Cookie for a refresh-token is not stored - node.js

I'm currently struggling with JWT and refresh tokens. I implemented the following flow from the OAuth 2 spec.
The user gets a JWT token which is used for requests and before it expires with the refresh token a new JWT is retrieved.
It works fine, the refresh token is properly returned by the API, but not stored in the cookies storage of the browser. The request for obtaining the token are using { withCredentials: true }
The way how the user gets the refresh token looks like the following. The web app runs on example1.com and the API server on example2.com, both run with https. Is there a error in my cookie configuration or some other mistake in my code?
const cookieOptions = {
httpOnly: true,
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
secure: true,
sameSite: 'none',
};
res.cookie('refresh_token', token, cookieOptions);
Thanks in advance!

The cookie will be considered third party and browsers drop these aggressively. Aim to convert this to a first party cookie instead, via these steps:
Web app origin is https://www.example.com
API runs at https://api.example.com - a sibling domain
Cookie domain is .example.com
This kind of domain relationship is a prerequisite to using secure cookies these days, due to recent browser restrictions to prevent tracking. I expect your code will work fine once the domain setup is correct.
For further info see this Curity Code Example and the use of a hosts file to test the domain setup. This example also encrypts the cookie using AES256 and uses the cookie setting SameSite=strict.

Related

How does session based authentication work since we can't share cookie between different domains?

I used google authentication in my MERN stack app using passport js. I have deployed frontend to vercel and backend to render. in my localhost, I can log in successfully and it returns cookies to my frontend from the backend. but in the deployed version when I try to log in it does log in but it doesn't return any cookies to the frontend. I did some research and found that cookies can't be shared between different domains. my question is should I host my backend in a subdomain and frontend in the main domain?
frontend: https://passport-frontend-seven.vercel.app/
backend : https://popupchat-backend.onrender.com/
// how i used express-session:
app.use(
session({
secret: "secretcode",
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
sameSite: "none",
secure: true,
domain: process.env.CLIENT_URL,
maxAge: 1000 * 60 * 60 * 24 * 7, // One Week
},
})
);
app.set("trust proxy", 1);
Yes, you are correct in your understanding that cookies can't be shared between different domains. If you have your frontend and backend deployed on different domains, then you will have difficulty accessing the cookies set by the backend in the frontend.
Hosting your backend on a subdomain of your main domain is one solution to this problem. For example, if your main domain is example.com, you can host your backend at api.example.com. This way, both the frontend and the backend will be under the same main domain and you will be able to share cookies between them.
However, there are other solutions as well, such as using a token-based authentication system, where the backend returns a token after a successful login, and the frontend can store this token in local storage or a cookie. The frontend can then send this token in the header of subsequent API requests to authenticate the user on the backend. This approach can be more secure than using cookies, since cookies are often stored in plain text and can be easily compromised.

How to issue httpOnly cookie using google auth

I have an API on api.domain.com which using local authentication issues a cookie if user authentication is a success.
The code to demonstrate the logic is right below.
ctx.cookies.set("token", token, {
httpOnly: true,
secure: true,
maxAge: 1000 * 60 * 60 * 24 * 14, // 14 Day Age
domain:"domain.com",
});
After that, the frontend which is hosted at client.domain.com can access that token such that it can be passed as an authorization bearer token, for that reason we all know, but I won't explain.
Everything was working fine until I decided that I wanted to use external providers for authentication.
I managed to integrate Github, and Google OAuth successfully and I can confirm that the authentication flow is flawless except that the httpOnly cookie isn't being issued therefore making it difficult to make requests to those authorized endpoints that require an authorization bearer token.
What I have tried
As above, with the domain option set to domain.com at first, client.domain.com secondly, accounts.google.com but no success up to now.
My question
How do I authenticate using these external providers such that my API api.domain.com can issue an httpOnly cookie
which can be used for that useful purpose on the frontend on client.domain.com when making requests?
Thanks in advance.

Next.js Authentication with JWT

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.

Session Management problem with http-only cookies Node.js,React

I just confused about session management. For session management , currently im using http-only cookies to store my JWT but these cookies cannot be reached by everyone because of browser's cookie settings which I think is bad for the user experience. So when i search about alternative ways like localstorage. I learned that you are not secure enough in these ways. What would you suggest me to do with the issue I mentioned above? Should i change entire auth system to server-side or any ideas ?
//AUTHENTICATE
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'None',
maxAge: 7 * 24 * 6 * 604800,
});
//LOGOUT
res.cookie('token', '', {
httpOnly: true,
secure: true,
sameSite: 'None',
maxAge: 1,
});
res.clearCookie('token');
You should use httpOnly cookie to prevent access from JS, (with xss attach you can execute JS on other users session).
My suggestion (which us base on The Ultimate Guide to handling JWTs on frontend clients) is to use 2 kind of tokens:
Refresh Token - stored on httpOnly cookie, is used to update the accessToken only, it is valid for long period (no recommended longer than 1 day)
Access Token - stored in memory, attached to each request that needs auth. valid for short period of time (10 mins).
The idea works like this:
User logins, your server validates the credentials and generate an httpOnly cookie with the refreshToken and returns as a response the accessToken.
Your client app sores the accessToken on some class instance (when used with Axios you can attach it as base Authorization header to all requests).
When your app makes a request, it adds the AccessToken as the Authorization header, if the AccessToken expires your api will return 403 UnAuthorized, your client app makes a request to special end point /auth/token with the httpOnly cookie which holds refreshToken, this end point validates the refreshToken, and returns a new accessToken with expiration time of 10mins (which your client app updates the base Auth header with), then your app can retry the previous failed request with the new accessToken.
With this method, there is no access to any kind of tokens from outside of your app.
The refreshToken is not accessible at all to your js and the accessToken is held in memory, only if your app has some flaw it will be exposed, and even if the attacker stole it, it is valid only for 10 mins (without the ability to get a new one because it doesn't have the refreshToken)
For more details read the article I've added.

Where to do JWT expiration with Node.js and jsonwebtoken?

I am creating a web application based on MEAN and I have the following question:
Where is it better to put a token (JWT) expiration? Right now what I am doing is to create a token without expiration and in the client, I created a cookie with that token and 10 minutes of expiration.
In that cookie, I add 10 minutes in every request I make. If the user is inactive for 10 minutes that cookie expires and the request is made without a token.
In Chrome:
F12 ➡️ Application tab ➡️ Cookies ➡️ Copy and paste token into a REST client like Postman
Whoops, I just got an everlasting token for your API!
In other words, as you suspected, this isn't a good way of doing things. The expiration should be in the token's payload - that way, you can verify nobody has altered it, as it'll be signed with your server secret value.
The Node JWT library actually has this functionality built-in:
jwt.sign({
// 1 hour expiration
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret');
This isn't to say that you can't/shouldn't also use cookie expiration with your tokens, but relying on it alone isn't secure.

Resources