0

I have issue with call online API from client. I created nestjs API with httponly credential and when

  • nestjs app hosted in local and client from local it's worked
  • also when nestjs app hosted in online server and client hosted in online server it's worked
  • but when nestjs hosted in online server and client call API from local get forbidden error.

nestjs main.ts:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const cookieSession = require('cookie-session');

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors({
    credentials:true,
    origin:['http://localhost:3000','http://test.nextu.top']
  });
  app.use(
    cookieSession({
      keys: ['asdasd'],
    }),
  );
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(5072);
}
bootstrap();

client fetch:

const doLogin = async () => {
    const bData = {
        Email: '********',
        Password: '****'
    }
    fetch("http://api.nextu.top:5072/auth/signin", {
        method: "POST",
        body: JSON.stringify(bData),
        headers: {
            "access-control-allow-origin": "*",
            'Content-Type': 'application/json;charset=UTF-8',
        },
        credentials: 'include'
    }).then(res => res.json()).then(data => {
        console.log(data);
        getUserInfo();
    })
}
const getUserInfo = () => {
    fetch('http://api.nextu.top:5072/auth/userinfo', {
        method: 'GET',
        headers: {
            "access-control-allow-origin": "*",
            'Content-Type': 'application/json;charset=UTF-8',
        },
        credentials: 'include'
    }).then(res => res.json()).then(data => console.log(data)).catch(err => console.log(err))
}

doLogin() working fine in each situation getUserInfo() don't work when call from client and nestjs app hosted in online server

  • getUserInfo() has AuthGurd in nestjs
  • getUserInfo() working fine in postman
  • forbiden error : forbiden error
  • Use the network tab to debug this. Start by looking at the `Cookie` headers on the requests and the `Set-Cookie` headers on the responses. – Quentin Aug 01 '22 at 09:36
  • You said you have a CORS error, but the screenshot next to it shows an HTTP 403 error and a log of a Response object. Neither of those are CORS errors. Did you screenshot the wrong thing? Or do you not have a CORS error? – Quentin Aug 01 '22 at 09:38
  • I couldn't see any cookie on network tab , maybe httponly hide cookies – Hossein Ashrafipoor Aug 01 '22 at 11:54
  • The `httponly` flag doesn't hide cookies from the Network tab. If you don't even see a `Set-Cookie` header on the response to the request to `http://api.nextu.top:5072/auth/signin` then you need to focus you attention on that. – Quentin Aug 01 '22 at 12:45

2 Answers2

0

I find a way to solve it: changed from:

   cookieSession({
     keys: ['asdasd']
   }),
 );

to:

  app.use(
    cookieSession({
      keys: ['asdasd'],
      sameSite: 'none'
    }),
  );

and run client and server on https.

  • sameSite: 'none' just work on https mode
  • 1
    It would be more secure to set the domain name instead of allowing cookies to any domains like you do here. None used to be the default for browsers, but newer browsers now default to Lax for better defense against cross-site request forgery (CSRF) attacks. – romslf Aug 02 '22 at 15:33
  • you right but it's just for development – Hossein Ashrafipoor Aug 03 '22 at 05:16
  • Then please consider accepting my answer as it is what user with the same problem as you should use and learn as best practice – romslf Aug 03 '22 at 07:57
-1

You should define on which domain the cookie is set, like so

app.use(cookieSession({
  keys: ['asdasd'],
  sameSite: 'strict',
  domain:'.nextu.top'
}));

Note that the '.' is very important as it tell that cookie can be set on any subdomains of "nextu.top"

It work as expected on dev because your front and back are on the same domain "localhost", the only thing changing is the port, but on prod your front is "test.nextu.top" and back "api.nextu.top" which are not the same and cause your issue

romslf
  • 71
  • 7
  • Why should that make a difference? If `http://api.nextu.top:5072/auth/signin` sets the cookie then it will be for `api.nextu.top`. – Quentin Aug 01 '22 at 09:37
  • Because your client/front need to send the cookie along with the request to the backend to authenticate itself – romslf Aug 01 '22 at 09:40
  • Yes, but it only needs to set it to the backend. The scope of the cookie doesn't need expanding to code the server hosting the frontend as well. – Quentin Aug 01 '22 at 09:41
  • I understand what you mean, but how can your back authenticate your client if your client can't send the authentication cookie to the backend when making requests that need authentication ? – romslf Aug 01 '22 at 09:45
  • Some JavaScript on example.com making an Ajax request to example.net will send cookies belonging to example.net **not** example.com. "if your client can't send the authentication cookie to the backend" doesn't make sense because it doesn't apply. – Quentin Aug 01 '22 at 09:46
  • That link **supports** what I just said. – Quentin Aug 01 '22 at 09:49
  • You cannot set or read cookies on CORS requests through JavaScript. Although CORS allows cross-origin requests, the cookies are still subject to the browser's same-origin policy, which means only pages from the same origin can read/write the cookie. From https://stackoverflow.com/questions/14462423/cross-domain-post-request-is-not-sending-cookie-ajax-jquery – romslf Aug 01 '22 at 10:00
  • "You cannot set or read cookies on CORS requests through JavaScript" — While this is true (for client-side JS), it isn't relevant since the cookie is being set by the HTTP response and not by JavaScript, and JavaScript isn't reading the cookie, it is being included in the HTTP request by the browser and read on the server. – Quentin Aug 01 '22 at 10:01
  • [I built a test case](https://gist.github.com/dorward/ed88f9b13d7a72e76fa10caf5b18ae96) and the cookies were set and read just fine on it (without changing `domain` from the default in the `cookieSession` settings … which is what the OP is using, not express.session) – Quentin Aug 01 '22 at 10:05
  • I tested your example and it print the following warnings: Cookie “session” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, without the “secure” attribute. To know more about the “SameSite“ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite Cookie “session.sig” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, without the “secure” attribute. To know more about the “SameSite“ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite – romslf Aug 01 '22 at 10:34
  • So what? That's just a warning. That code was a quick demo, not production code that needs to work in browsers next year. If it **was** production code, then I'd be running it on HTTPS, so the server flag would be set automatically by the cookie-session module. – Quentin Aug 01 '22 at 10:37
  • @Quentin I played a bit more with your example and read the cookie-session doc, I think your example works because by default cookie-session use `sameSite: false`, but using `sameSite: "strict"` and specifying domain it works as expected and should be considered more safe – romslf Aug 02 '22 at 16:56