Search code examples
javascriptnode.jsexpresscookiessetcookie

Unable to access cookie from fetch() response


Even though this question is asked several times at SO like:

fetch: Getting cookies from fetch response

or

Unable to set cookie in browser using request and express modules in NodeJS

None of this solutions could help me getting the cookie from a fetch() response

My setup looks like this:

Client

export async function registerNewUser(payload) {
    return fetch('https://localhost:8080/register',
        {
            method: 'POST',
            body: JSON.stringify(payload),
            credentials: 'same-origin',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json'
            }
        });
}

...
function handleSubmit(e) {
    e.preventDefault();

    registerNewUser({...values, avatarColor: generateAvatarColor()}).then(response => {
        console.log(response.headers.get('Set-Cookie')); // null
        console.log(response.headers.get('cookie')); //null
        console.log(document.cookie); // empty string
        console.log(response.headers); // empty headers obj
        console.log(response); // response obj
    }).then(() => setValues(initialState))
}

server

private setUpMiddleware() {
    this.app.use(cookieParser());
    this.app.use(bodyParser.urlencoded({extended: true}));
    this.app.use(bodyParser.json());
    this.app.use(cors({
        credentials: true,
        origin: 'http://localhost:4200',
        optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
        credentials: true
    }));
    this.app.use(express.static(joinDir('../web/build')));
}
...
this.app.post('/register', (request, response) => {
    const { firstName, lastName, avatarColor, email, password }: User = request.body;
    this.mongoDBClient.addUser({ firstName, lastName, avatarColor, email, password } as User)
        .then(() => {
            const token = CredentialHelper.JWTSign({email}, `${email}-${new Date()}`);
            response.cookie('token', token, {httpOnly: true}).sendStatus(200); // tried also without httpOnly
        })
        .catch(() => response.status(400).send("User already registered."))
})

enter image description here


Solution

  • I figured it out. The solution was to set credentials to 'include' like so:

    export async function registerNewUser(payload) {
        return fetch('https://localhost:8080/register',
            {
                method: 'POST',
                body: JSON.stringify(payload),
                credentials: 'include',
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/json'
                }
            });
    }
    

    After that I needed to enabled credentials in my cors middleware:

        this.app.use(cors({
                credentials: true, // important part here
                origin: 'http://localhost:4200',
                optionsSuccessStatus: 200 
            })
    

    And then finally I needed to remove the option {httpOnly: true} in the express route response:

    response.cookie('token', '12345ssdfsd').sendStatus(200);
    

    Keep in mind if you send the cookie like this, it is set directly to the clients cookies. You can now see that the cookie is set with: console.log(document.cookie).

    But in a practical environment you don't want to send a cookie that is accessible by the client. You should usually use the {httpOnly: true} option.