skip to Main Content

I know there are a number of questions posted around this and I have read through those, but I cannot seem to solve this issue.

The Set Up
I have my react app running on localhost:3000. My Express Server is running on port 8000 (or 3001 if 8000 is not available. I am using a database to both store the session for the server and as user details. It is a Postgress SQL database running on the default port of 5432 for development.

What works
So, I can fill in the user details in the browser accessing via port localhost:3000. This get gets dispatched via createAsyncThunk which then passes to an API post request. I can see that this is received in the Server, validations are completed, an express session is created, saved in the postgres data base and a response with the user object and a header: set cookie is returned.

The issue
The cookie is not being saved in the browser (I have tried using Chrome and FireFox). I think from reading issues that others had that the problem might be around the configuration of the cookie or cors, but I am not sure.

Here is my code
Backend Express Server 8000
Server Set up

// set up imports
const express = require('express');
require('dotenv').config();
const passport = require('passport');
const initializePassport = require('./controllers/auth.js');
const helmet = require('helmet');
const cors = require('cors');
const session = require('express-session');
const { DB, SS } =require('./config');

//route imports
const { registerRouter, signinRouter, logoutRouter, orderRouter, userRouter } = require('./routes/userRoute.js');
const { productRouter } = require('./routes/productRoute.js');
const { cartRouter } = require('./routes/cartRoute.js');

//server setup
const app = express();

// Used for testing to make sure server / express app is running.
app.get('/', (req, res, next) => {
    res.send('<h1>Hello</h1>');
});

app.use(cors({
    origin: "localhost:3000",
    methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'DELETE', 'HEAD'],
    credentials: true,
}));
app.use(helmet());
const pgSession = require('connect-pg-simple')(session);
const { PORT } =require('./config');
const port = PORT || 3001;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));


const options = {
    user: DB.DB_USER, 
    host: DB.DB_HOST,
    database: DB.DB_DATABASE,
    password: DB.DB_PASSWORD,
    port: DB.DB_PORT,
    createDatabaseTable: true,
    createTableIfMissing: true
};

console.log(options); //After testing this line can be deleted.

const sessionStore = new pgSession(options);

app.use(session({
    name: SS.SS_SESS_NAME,
    resave: false, 
    saveUninitialized: false, 
    store: sessionStore,
    secret: SS.SS_SESS_SECRET,
    cookie: {
        maxAge: Number(SS.SS_SESS_LIFETIME),
        sameSite: 'lax', 
        secure: false, 
        domain: "http://localhost:3000",
        httpOnly: true,
        hostOnly: false,
    } 
}));

app.use(passport.initialize());
app.use(passport.session());

initializePassport(passport);

//configuration to allow the front end to store cookies created on the server:
app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "localhost:3000"); 
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    res.header("Access-Control-Allow-Credentials", true); 
    res.header("Access-Control-Allow-Methods", "GET, POST, PUT, HEAD, DELETE");  
    next();
});

//route for users
app.use('/api/users/register', registerRouter);
app.use('/api/users/signin', signinRouter);
app.use('/api/users/logout', logoutRouter);
app.use('/api/users/orders', orderRouter);
app.use('/api/users/details', userRouter);

//route for products
app.use('/api/products', productRouter);

//route for cart
app.use('/api/cart', cartRouter)

//app.listen
app.listen(port, () => {
    console.log(`Your server is listening on port: ${port}`);
});

Passport set up

const passport = require('passport'); 
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');

const pool = require('../db/index');

module.exports = (passport) => {
    const query = async (queryString, params) => {
        return await pool.query(queryString, params);
    };

    passport.use(
        new LocalStrategy ( async ( username, password, done) => {
            try {
                const querySchema = { name: 'user', email: `${username}`};
                const queryString = `SELECT * FROM user_customer WHERE email = $1`;
                const userResult = await query(queryString, [querySchema.email]);

                if (userResult.rows.length === 0 ) {
                    return done(null, false, { message: 'User not found' });
                }

                const foundUser = userResult.rows[0];

                const isMatch = await bcrypt.compare(password, foundUser.password);
                if (isMatch) {
                    return done(null, { 
                        id: foundUser.id,
                        email: foundUser.email,
                        firstName: foundUser.first_name,
                        lastName: foundUser.last_name
                    });
                } else {
                    return done(null, false, {message: 'Incorrect password '});
                }
            } catch (err) {
                console.error('Error during authentication. ' + err);
                return done(err);
            }
        })
    );

    passport.serializeUser((user, done) => {
        done(null, user.id);
    });

    passport.deserializeUser(async (id, done) => {
        try {
            const userResult = await query('SELECT id, email, first_name, AS firstName, last_name AS lastName FROM user_customer WHERE id = $1',
                [id]
            );
            if (userResult.rows.length === 0) {
                return done(new Error('User not found'));
            } return done(null, userResult.rows[0].id);
        } catch(err) {
            done(err);
        }
    });
};

module.exports.isAuth = (req, res, next) => {
    if (req.isAuthenticated()) {
        next();
    } else {
        res.status(401).json({ msg: 'You are not authorized to view this resource' });
    }
}

Front end React App3000
I have not included the login page code, but can if needed?

Redux Store

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { register, signinUser } from '../apis/apiRequest';

//REGISTER USER DETAILS REMOVED FOR SIZE

export const loginUser = createAsyncThunk('auth/loginUser', async (credentials, { rejectWithValue }) => {
    try {
        const response = await signinUser(credentials);
        console.log(response);
        if (!response.user) {
            console.warn(`Unable to signin user due to: ${response.message}`);
            return rejectWithValue(response.message);
        } else {
                      
            return response;
        }
    } catch (error) {
        console.error('Error logging in user: ', error);
        return rejectWithValue(error.message);
    }
});

const initialState = {
    isUserLoading: false,
    isAuthenticated: false,
    error: null,
    data: []
};

const authSlice = createSlice({
    name: 'auth',
    initialState,
    reducers: {},
    extraReducers: builder => {
        builder
            //REGISTER USER CASES REMOVED FOR SIZE//
            .addCase(loginUser.pending, (state) => {
                state.isUserLoading = true;
            })
            .addCase(loginUser.fulfilled, (state, action) => {
                state.isUserLoading = false;
                state.isAuthenticated = true;
                state.data = action.payload;
            })
            .addCase(loginUser.rejected, (state, action) => {
                state.isUserLoading = false;
                state.error = action.payload.message;
            })
    }
});

export default authSlice.reducer;
export const authData = state => state.auth.data;
export const userAuthLoading = state => state.auth.isUserLoading;
export const userAuthError = state => state.auth.error;

API Request

const API_ROOT = 'http://localhost:8000/api';

//I HAVE REMOVED THE API CALL FOR REGISTER AS I DONT THINK IT IS NEEDED

export const signinUser = async (credentials) => {
    const { username, password } = credentials;
    const response = await fetch (`${API_ROOT}/users/signin`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            username, 
            password
        }),
        withCredentials: true,
    });

    const json = await response.json();
    return json;
}

Following the request to log in, I am expecting (assuming that the login details are correct) that the server sends a response back to the app which contains the session passport cookie in the header as well as the user object. The Browser stores this this cookie for use later and I can access the user in my redux store.

Here are the screen shots of the request:
Screen shot showing the request object as it goes out and the objects when they return

Details from the networks tab showing the response from the server and the set-Cookie option in the header showing the session cookie

Here is the cookie in more detail

No cookie or session in memory of the browser

2

Answers


  1. Chosen as BEST ANSWER

    OK, so the response from Hadi was helpful. I compared what I previously had with what they suggested, and I think the only bits that I changed was the domain: 'localhost' in cookie session settings (previously domain: "http://localhost:3000",) and in the API in the front end client (react app) removed 'withCredentials: true,' and replaced with 'credentials: "include"'.

    Just tested it now after making those changes and session cookie is stored in the cookies of the browser.


  2. Correction:
    app.use(cors({
    origin: "http://localhost:3000&quot;,
    methods: [‘POST’, ‘PUT’, ‘GET’, ‘OPTIONS’, ‘DELETE’, ‘HEAD’],
    credentials: true,
    }));

    Cookie settings
    app.use(session({
    name: SS.SS_SESS_NAME,
    resave: false,
    saveUninitialized: false,
    store: sessionStore,
    secret: SS.SS_SESS_SECRET,
    cookie: {
    maxAge: Number(SS.SS_SESS_LIFETIME),
    sameSite: ‘lax’,
    secure: false,
    domain: "localhost",
    httpOnly: true,
    hostOnly: false,
    }
    }));

    Request Headers API Call

    export const signinUser = async (credentials) => {
    const { username, password } = credentials;
    const response = await fetch(${API_ROOT}/users/signin, {
    method: ‘POST’,
    headers: {
    ‘Content-Type’: ‘application/json’,
    },
    credentials: ‘include’,
    body: JSON.stringify({
    username,
    password
    }),
    });

    const json = await response.json();
    return json;
    

    }

    Access-Control Headers
    app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "http://localhost:3000&quot;);
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    res.header("Access-Control-Allow-Credentials", true);
    res.header("Access-Control-Allow-Methods", "GET, POST, PUT, HEAD, DELETE");
    next();
    });

    Your problem will be fixed with the codes I gave
    good luck

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search