Not able to set cookie from the express app hosted on Heroku - node.js

I have hosted both frontend and backend in Heroku.
Frontend - xxxxxx.herokuapp.com (react app)
Backend - yyyyyy.herokuapp.com (express)
I'm trying to implement Google authentication. After getting the token from Google OAuth2, I'm trying to set the id_token and user details in the cookie through the express app.
Below is the piece of code that I have in the backend,
authRouter.get('/token', async (req, res) => {
try {
const result = await getToken(String(req.query.code))
const { id_token, userId, name, exp } = result;
const cookieConfig = { domain: '.herokuapp.com', expires: new Date(exp * 1000), secure: true }
res.status(201)
.cookie('auth_token', id_token, {
httpOnly: true,
...cookieConfig
})
.cookie('user_id', userId, cookieConfig)
.cookie('user_name', name, cookieConfig)
.send("Login succeeded")
} catch (err) {
res.status(401).send("Login failed");
}
});
It is working perfectly for me on my local but it is not working on heroku.
These are the domains I tried out already - .herokuapp.com herokuapp.com. Also, I tried out without specifying the domain field itself.
I can see the Set-Cookie details on the response headers but the /token endpoint is failing without returning any status code and I can't see the cookies set on the application tab.
Please see the below images,
I can't see any status code here but it says it is failed.
These are cookie information that I can see but it is not available if I check via application tab.
What am I missing here? Could someone help me?

May you should try secure as:
secure: req.secure || req.headers['x-forwarded-proto'] === 'https'

You are right, this should technically work.
Except that if it did work, this could lead to a massive security breach since anyone able to create a Heroku subdomain could generate a session cookie for all other subdomains.
It's not only a security issue for Heroku but also for any other service that lets you have a subdomain.
This is why a list of domains has been created and been maintained since then to list public domains where cookies should not be shared amongst the subdomains. This list is usually used by browsers.
As you can imagine, the domain heroku.com is part of this list.
If you want to know more, this list is known as the Mozilla Foundation’s Public Suffix List.

Related

HTTP cookies are not working for localhost subdomains

I have a node/express API that will create an HTTP cookie and pass it down to my React app for authentication. The setup was based on Ben Awad's JWT HTTP Cookie tutorial on Youtube if you're familiar with it. Everything works great when I am running the website on my localhost(localhost:4444). The issue I am now running into is that my app now uses subdomains for handling workspaces(similar to how JIRA or Monday.com uses a subdomain to specify a workspace/team). Whenever I run my app on a subdomain, the HTTP cookies stop working.
I've looked at a lot of threads regarding this issue and can't find a solution, no matter what I try, the cookie will not save to my browser. Here are the current things I have tried so far with no luck:
I've tried specifying the domain on the cookie. Both with a . and without
I've updated my host file to use a domain as a mask for localhost. Something like myapp.com:4444 which points to localhost:4444
I tried some fancy configuration I found where I was able to hide the port as well, so myapp.com pointed to localhost:4444.
I've tried Chrome, Safari, and Firefox
I've made sure there were no CORS issues
I've played around with the security settings of the cookie.
I also set up a ngrok server so there was a published domain to run in the browser
None of these attempts have made a difference so I am a bit lost at what to do at this point. The only other thing I could do is deploy my app to a proper server and just run my development off that but I really really don't want to do that, I should be able to develop from my local machine I would think.
My cookie knowledge is a bit bare so maybe there is something obvious I am missing?
This is what my setup looks like right now:
On the API I have a route(/refresh_token) that will create a new express cookie like so:
export const sendRefreshToken = (res: Response, token: string): void => {
res.cookie('jid', token, {
httpOnly: true,
path: '/refresh_token',
});
};
Then on the frontend it will essentially run this call on load:
fetch('http://localhost:3000/refresh_token', {
credentials: 'include',
method: 'POST'
}).then(async res => {
const { accessToken } = await res.json()
setState({ accessToken, workspaceId })
setLoading(false)
})
It seems super simple to do but everything just stops working when on a subdomain. I am completely lost at this point. If you any ideas, that would be great!
if httpOnly is true, it won't be parsable through client side js. for working with cookies on subdomains, set domain as the main domain (xyz.com)
an eg in BE:
res.cookie('refreshToken', refreshToken, {
domain: authCookieDomain,
path: '/',
sameSite: 'None',
secure: true,
httpOnly: false,
maxAge: cookieRefreshTokenMaxAgeMS
});
and on FE add withCredentials: true as axios options or credentials: include with fetch, and that should work

Not able to receive/set cookies in browser from backend in MERN app with backend hosted on heroku and frontend on netlify

I have a MERN app whose backend is hosted on Heroku and frontend on netlify. I am not able to see the cookies in the browser after deploying the app but it works fine on localhost. I think it is due to different backend and frontend ports, what am I missing, please help
You are correct. Cookies are not cross-domain compatible. If it was, it would be a serious security issue. The easiest way to fix your problem would be to send back the cookie as a res object, and then setting the cookie manually in the frontend.
Take this for example. I'll do this with JavaScript style pseudocode. Don't copy paste this as this most likely wouldn't work right away. This is what you're going to do on the back-end:
// 1. Authenticate the user.
const userData = await authenticateUser();
const userToken = await verifyUser(userData);
// 2. Return the response object.
return response.status(200).json({
status: 'success',
data: userData,
token: userToken,
});
In the front-end:
const response = await axios.post(...); // your API call, will return above object.
// set your authentication token here.
// the 'options' object will contain all possible cookie options, example would be 'secure'.
cookies.set('token', response.data.token, options);
// alternatively, set the cookie in the local storage.
localStorage.setItem('token', response.data.token);
You need to set the cookie accordingly in the front-end.
Further reading: MDN Docs
EDIT: Your question is unclear. First time you talked about cookies, but now you're talking about httpOnly cookies. Please be more specific in your questions.
It is impossible to set httpOnly cookies in React.js if it is cross-domain. React is only responsible for the 'view' of the website. httpOnly cookies are only meant to be set server-side. Client-side JavaScript cannot read or set that specific cookie, but it is able to send it. Unless you have something in your Netlify that can do server-side operations, I don't think that is possible.
Your best bet is to actually just use the same domain.

Keycloak always redirecting to login page

I´m using a keycloak instance to login in the frontend and secure the backend-api. After deployment on a linux machine on aws I faced a issue. I´m getting constantly redirected to the login page by accessing the api with a jwt token. Locally it´s working fine.
My client is a confidential client. I´m using client_id and _secret to authorize for the token call. The jwt token is valid and sucessfully generated.
My implementation of the api works with expressJs and the keycloak-nodejs-connector:
keycloakConfig = {
serverUrl: 'https://keycloak.myserver.com/auth',
realm: 'examplerealm',
clientId: 'ui-client'
};
public init() {
if (this.keycloak) {
console.warn("Trying to init Keycloak again!");
return this.keycloak;
}
else {
console.log("Initializing Keycloak...");
const memoryStore = new session.MemoryStore();
// #ts-ignore
this.keycloak = new Keycloak({ store: memoryStore }, this.keycloakConfig );
return this.keycloak;
}
}
I could imagine that it is dependent on the current https setting. My nodejs api provides a endpoint for http and https (locally with a self signed certificate). On the server, where keycloak is running, I added a letsencrypt certificate with certbot and everything looks fine in the browser.
Keycloak is started with the docker-container jboss/keycloak.
I´m curious to figure out my current issue and help is very appreciated :slight_smile: Let me know, if I missed to add necessary informations.
Thanks in advance.
Dominik
I found a solution for this.
First I updated to the latest version of keycloak-connect. They provided a new major version 12 and it seems there was a change about the configuration.
Second there was a issue with the configuration. I digged into the current config object and figured out, that it should look like this:
keycloakConfig = {
realm: 'test-realm',
authServerUrl: 'https://myurl/auth/',
realmPublicKey: 'key'
};

Preventing man-in-the-middle attacks with user authentication (Node/Vue/Passport)

I currently have a webapp I'm writing in Node/Vuejs with Passport handling authentication, and I've run into a problem. I was thinking about how I have authentication currently set up and I realized I had a glaring security hole.
In short, I have my Vuex store hitting a local API endpoint /api/me. That endpoint does a simple return of req.user. For the sake of brevity, a typical response looks like this:
{
username: 'Bob',
roles: [] // normal user has no roles,
email: 'someguy#bob.com'
}
My admin route /admin has a beforeEnter check, as shown below, that incorporates this check using the Vuex store, so I can have a cached version of user data accessible on the frontend.
{
path: '/admin',
name: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
store.dispatch('getMe').then(() => {
if (store.getters.user.roles && store.getters.user.roles.includes('administrator')) {
next();
return;
}
next({ path: '/' });
});
}
}
Here's the thing though - I realized that someone could easily game the system. In fact, I tried it myself with a test, non-Adminstrator account, and I was able to get in by returning the following from a local server set up for this purpose in Postman:
{
username: 'Super Admin Joe',
roles: ['administrator'] // normal user has no roles,
email: 'admin#bob.com'
}
And viola! The user now has full access to admin pages.
My question is, how could I prevent against this?
I need to check that the user is authenticated on every page, but a potential attacker could quite easily proxy any request (in this case it's /api/me) to make themselves any user they want. They can login normally with their own account, open the Network tab and copy the response payload, then change the user data as they wish. There needs to be some sort of encryption between the frontend and backend when checking a users' logged-in status, I believe.
I tried thinking about how I could prevent this from happening, but anything on my end (server-side, at least) seems useless as any request could easily be redirected to an attacker's local machine.
Any advice on how to "sign" my requests to make sure they aren't being proxied? Thanks in advance!
You shouldn’t have to be signing the response body of an api request. The typical way to do authentication is to establish a signed session cookie that acts either as an identifier to session information in an external database, or contains session information itself. This cookie should be in the header of your response and passport should give you a way to administer this cookie without you even realizing it.
This way the user can’t tamper with the information sent from the server in a way that’s easy to detect, and since it’s a cookie it will automatically be sent with each request by your browser (although if you’re using some AJAX library you may have to explicitly specify you’d like to send the cookie). What MadEard was referring to in the comment is where the cookie information is able to be accessed using passprt which is the ‘user’ property in the ‘req’ object.
After reading your github files:
server.get("/admin", function(req, res){
if(req.user && req.user.roles.includes("administrator")){
//user is an administrator, render the admin panel view
}else{
//is not an admin, redirect or send error message
}
});
In every Express route, after authentication with Passport, you have the req.user object.
It is established by checking the request cookie connect.sid, and checking which session this cookie belongs to on the server.
As such, you can trust that in any Express route, the object req.user contains the information relevant to that cookie and you can act upon it.
A little note: doing server-side validation should become a reflex for you over time.
The client is meant to display information. If, at any point, you are making the client take any decision that could be a security liability, take a step back and think it again.

redirect to another app with session token (jwt) in AngularJS and NodeJS

I have a startup module in angularjs. This module is just to login and have public information (login, prices, newsletter...). I have many roles and for each role, i have an app (angular module). I made this architecture because i have complex module for each role and it was impossible to put all roles in one module.
So, for login, i use jsonwebtoken in node like this :
var token = jwt.sign(user, config.secureToken, { expiresInMinutes: 20*5});
res.json({ token: token, user: user });
It works perfectly. I can login into my app. After that, i have to propose a list of roles to redirect to the right module.
In angular, I have AuthHttp service that adds security headers (with token) to call rest service with $http.
How can i redirect to 'mydomain:port/anotherModule' with $location or $http ?
With this code in nodejs :
app.get('/secondModule', expressJwt({secret: config.secureToken}), function (req, res) {
res.render('restricted/secondModule/index.html');
});
NodeJs sends an html code in response and does'nt redirect...
And if i do this in my angular controller :
location.href = route;
i have this result on nodejs console :
Error: No Authorization header was found
I am not sure about the libraries you are using, but issue seems that you are loosing the token because you navigate to a altogether new page.
Based on your auth library you need to pass the token that you get after auth from one page to another.
The options here are to either use browser sessionStorage or querystring to pass the token along and at it back to the http header collection on the new page (module)
This is an old post but I recently took a long time to figure this out. I may be wrong but I believe nodeJS/expressJS can't read the token from the session storage. I believe you will need to pass the token via the request header using AngularJS.
This depends on the front end that you are using. For me, I am using AngularJS and I have to do something like this.
angular.module('AngularApp').factory('authFactory',
function($window){ //the window object will be able to access the token
var auth = {};
auth.saveToken = function(token){
$window.localStorage['token_name'] = token; //saving the token
}
auth.getToken = function(){
return $window.localStorage['token_name']; //retrieving the token
}
return auth;
}
.service('authInterceptor, function(authFactory){
return { headers: {Authorization: 'Bearer "+ authFactory.getToken()}
} //the last line gets the retrieved token and put it in req.header
Then, you just need to include 'authInterceptor' in all the http methods when you communicate with the backend. This way, nodeJS will be able to pick up the token.
You can see the Authorization field in req.header if you use the chrome developer tool and look at the Network tab. Hope this helps.

Resources