Search code examples
node.jsreactjsexpresspassport.jsexpress-session

Authorization bug on production


I wrote an app with PassportJS, Express at backend and react, axios at frontend. When i start it on localhost authorization works fine, i pass post request from client with email and password, then server gets it, and return Set-Cookie header in response, cookie are stored in the browser. But when i deploy my backend and frontend on Vercel cookies are not working properly when i pass requests from client, when i use Postman it is ok.

app.use(
  cors({
    origin: process.env.ADMIN_PANEL_URL,
    methods: 'GET,POST,PUT,DELETE',
    credentials: true,
    exposedHeaders: ['set-cookie'],
  })
);

app.set('trust proxy', 1);

app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: true,
    cookie: {
      sameSite: 'none',
      secure: true,
      maxAge: 1000 * 60 * 60 * 24,
    },
  })
);

This is how I set up cors and session for production, when I pass data with email and password from client to server, I get Set-Cookies in response headers, cookies are not stored in browser in admin panel, but when I open server by url -address and try to pass secure endpoint it returns data to me but when i pass same endpoint from axios on client it contains Cookie request header with same cookie as when request to server but returns 401 error not passing middleware , while in the response headers there is a Cookie header that contains a cookie that does not match the expected. Sometimes if i make request from client i pass middleware and get my data.

const api = axios.create({
  baseURL: `${process.env.REACT_APP_SERVER_URL}`,
  withCredentials: true,
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "Access-Control-Expose-Headers": "Set-Cookie",
  },
});

static async getStatistics() {
    return (
      await api.get<IStatistic>(`/statistics`, {
        withCredentials: true,
      })
    ).data;
  }

static localLogin(
    email: string,
    password: string,
    setState: Dispatch<SetStateAction<boolean>>,
    setAlert: Dispatch<
      SetStateAction<{
        isOpen: boolean;
        message: string;
        severity: AlertColor;
      }>
    >
  ) {
    axios({
      method: "POST",
      data: {
        email: email,
        password: password,
      },
      withCredentials: true,
      url: `${process.env.REACT_APP_SERVER_URL}/local`,
    })
      .then((res) => {
        if (res.status === 200) {
          if (res.data.isAdmin) {
            localStorage.setItem(`isAuth`, JSON.stringify(true));
            window.location.href = `${process.env.REACT_APP_CLIENT_URL}`;
          } else {
            setState(true);
          }
        }
      })
      .catch((e) => {
        console.log(e);
        if (e.code !== "ERR_NETWORK" && !e.response.data.success) {
          setState(true);
        } else {
          setAlert({
            isOpen: true,
            message: "Server error, try again later",
            severity: "error",
          });
        }
        console.log(e);
      });
  }

This is how I wrote requests using axios on the client

router.post(
  '/local',
  passport.authenticate('local', {
    failureRedirect: '/login/fail',
  }),
  (req, res) => {
    res.json(req.user);
  }
);

passport.use(
  'local',
  new PassportLocal.Strategy(
    { usernameField: 'email' },
    async (email, password, done) => {
      try {
        const user = await User.findOne({ email: email.toLowerCase() });
        if (!user || !(await bcrypt.compare(password, user.password))) {
          done(null, false);
        }
        done(null, user);
      } catch (e) {
        done(e);
      }
    }
  )
);

passport.serializeUser((user, done) => {
  return done(null, user._id.toString());
});

passport.deserializeUser((id, done) => {
  User.findById(id, (err, user) => {
    return done(err, user);
  });
});

This is passport LocalStrategy

export const isAdminLoggedIn = (req, res, next) => {
  req.isAuthenticated() && req.user.isAdmin ? next() : res.sendStatus(401);
};

This is middleware


Solution

  • I changed my cors set up, added CookieParser and changed expressSession to cookieSession (i notice that expressSession always generated different cookie values with the same data and rewrite my cookies, but cookieSession generate the same cookie value with the same data so even if it changes it write the same value)

    app.use(
      cors({
        origin: process.env.ADMIN_PANEL_URL,
        methods: 'GET,POST,PUT,DELETE',
        credentials: true,
      })
    );
    
    app.use(cookieParser());
    app.set('trust proxy', 1);
    
    app.use(
      cookieSession({
        name: 'session',
        secret: process.env.SESSION_SECRET,
        sameSite: 'none',
        secure: true,
        maxAge: 1000 * 60 * 60 * 24,
        path: '/',
        httpOnly: true,
      })
    );

    And i remove all optional values from axios instanse as Alexandar sad

    const api = axios.create({
      baseURL: `${process.env.REACT_APP_SERVER_URL}`,
      withCredentials: true,
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
      },
    });

    Cookies are working fine now, hope the answer helps someone