3

As a start I want to say that I already read almost everything regarding this issue and so far there is no resolution.

In short I have a Node.js API server running on localhost:3000. I also have an Angular 10 app running on localhost:4200. The problem is simple - I make a request to localhost:3000/api/users/login and receive the following: server-response

However there is no cookie saved as you can see here: empty-cookies

As a result every subsequent request be it POST or GET is without headers and the server cannot recognize the user. So far I tried:

  • including the { withCredentials: true } to the http requests from angular
  • changing the cookie to http: false
  • setting the domain in the cookies to various stuff like '.app.localhost'
  • adding sameSite: 'none' to the cookie
  • searching for the cookie on localhost:3000 where the API is running
  • changing the origin in cors to '*' or removing it entirely
  • tried all these things in Edge as well(otherwise I use Chrome)

Unfortunately none of these things worked for me. Otherwise the login and registration are done succesfully and I can see the data in mongo.

Here I will add the snippets of the code I use:

const app = express();
app.use(express.json());
app.use(cookieParser(config.cookieSecret));
app.use(express.static(path.resolve(__basedir, 'static')));
app.use(cors({
  origin: config.origin,
  credentials: true
}));
app.use('/api', apiRouter);
app.use(errorHandler);

This is the login handler in which I set the cookie:

async function login(req, res, next) {
const { email, password } = req.body;
try {
    let user = await User.findOne({ email });
    const match = await user.matchPassword(password);
    if (!match) {
        res.status(401)
            .send({ message: 'Wrong username or password' });
        return
    }
    user = bsonToJson(user);
    const token = utils.jwt.createToken({ id: user._id });
    if (process.env.NODE_ENV === 'production') {
        res.cookie(authCookieName, token, { httpOnly: true, sameSite: 'none', secure: true })
    } else {
        res.cookie(authCookieName, token, { httpOnly: true })
    }
    res.status(200).send(user);
} catch (err) {
    next(err);
}

}

And here is the Angular part:

loginMet(data) {
return this.http.post('http://localhost:3000/api/users/login', data, {withCredentials: true});

}

3 Answers3

1

Update

After testing it out it appears that chrome (86.0.4240.198) still allows cross site cookies on localhost which means that your problem is not caused by the new restrictions and using { useCredentials: true } should work fine.

Initial

For two locations to be considered to have the same origin, the protocol, domain and the port have to be the same. In your case you have (localhost:3000 and localhost:4200) and cookies cannot be shared across different origins in Safari, Firefox and from 2020 in Chrome as well.

You can read more on the topic here.

Locally you can try to solve this issue by creating proxy.config.json in order for the webpack dev server to act as a proxy to your backend. So all requests to localhost:3000 will be instead sent to localhost:4200. You can read more about it here.

Another solution that will work locally and on production (in the case that you won't be serving the angular app from the same server as the one for the API) is to have one domain and two subdomains for the individual apps. Locally you can do this by updating the hosts file and on production of course this will be done by the DNS server.

user1
  • 1,035
  • 1
  • 9
  • 17
  • Thank you for the resources, unfortunately after creating the proxy.config.json in the src/ folder and adding it to angular.json -> "architect" -> "serve" -> "options" -> "proxyConfig": "src/proxy.config.json" as written in the angular site did not work for me. I used the default configuration given there only changing the target because in my case the API is on port 3000. I tried theirs as well with the target being 3000 but still no success. My config file looks like this: { "/api": { "target": "http://localhost:4200", "secure": false } } –  Nov 27 '20 at 13:34
  • Using the default configuration won't make things work for you automatically. You have to play around with the configuration. Try changing the json to a js file like shown [here](https://angular.io/guide/build#bypass-the-proxy) and try [this](https://stackoverflow.com/a/57784987/2477642) or [this](https://github.com/facebook/create-react-app/issues/2778#issuecomment-383266751). – user1 Nov 27 '20 at 15:21
  • I found what the problem was. {withCredentials: true} doesn't work with http.post for some reason. When I changed the method to GET both in the server and in Angular the request headers were sent. –  Nov 28 '20 at 20:58
  • This. The proxy help me. My proxy config file look like this: { "/api": { "target": "http://localhost:3000", "secure": false } } And now my request go to localhost:4200/api instead of localhost:3000/api and the proxy redirect to the correct address. Thanks dude! – Freddx L. Apr 29 '21 at 00:02
1

The answer of user1 is partially correct.

As of February 2020 RFC 6265 changed "HTTP State Management Mechanism". Cookies are now stored with default value LAX which forces the browser to not provide the cookie to third party.

This was adopted by Firefox v69 and by Google Chrome (about a year later) in version 80.

To avoid it you must set the following when cookie is created:

SameSite=none secure=true

Note: Those props should be used in development mode only as they in some way expose the client to a vulnerability!

Sources:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite https://www.rfc-editor.org/rfc/rfc6265#section-4.1

Community
  • 1
  • 1
  • Usually the [Secure](https://owasp.org/www-community/controls/SecureFlag) flag is used when you have a secure connection (HTTPS). – user1 Nov 28 '20 at 07:46
  • Thanks for the information. The problem was indeed fixed when using {withCredentials: true}. I have no idea why it did not work the first time I tried it. No further changes to the code were made. –  Nov 28 '20 at 19:46
0

I have an answer that will work on the production.

You need to add the Domain field to the cookies setup. For example:

  1. Your web application on domain: web.excample.com
  2. Your api on domain: api.excample.com

You must specify that your web and api applications will be located on the same domain, but on different subdomains.

Domain=.excample.com

Cookies:

res.setHeader('Set-Cookie', 'jwt=YOUR_TOKEN; HttpOnly; Domain=.excample.com; Path=/; SameSite=none; secure; Max-Age=31536000') 
// you can also do this with res.cookies
// SameSite can be either NONE or STRICT

You also need to add this headers:

response.header('Access-Control-Allow-Origin', 'https://web.excample.com');
response.header('Access-Control-Allow-Credentials', true);
response.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');

And in axios requesting you need to add withCredentials:true attribute:

axios.post(`https://api.excample.com/refresh-tokens`,{},{withCredentials: true})

Then you will be able to get your cookies back on api server.