0

I am trying to build a React frontend app to fetch data from a backend Express server with PassportJS. I have tried using Postman to login and verify that the session is being stored in the server side until I logout. It works.

Then I tried to do the same stuff on my React app but when I get the user information (successful login) all the further fetchs are being rejected (403) according to a "rule" I set up on the server side.

export const checkSession = (req, res, next) => {
    if (req.path === '/api/auth/login' || req.path === '/api/auth/register') {
        next();
        return;
    }

    if (!req.user) {
        res.status(403).json({ error: 'Access not allowed' });
        return;
    }

    next();
};

Again, this works great with Postman.

This is my login form:

import React, { useState, useEffect, useRef, useContext } from 'react';
import { Redirect } from 'react-router-dom';
import { SERVER_URL } from '../../utils/config';
import axios from 'axios';
import { 
    Container, 
    Row, 
    Col, 
    Form, 
    Button 
} from 'react-bootstrap';
import { NotificationManager } from 'react-notifications';
import { UserContext } from '../../context/UserContext';

export default () => {
    const userContext = useContext(UserContext);
    const emailRef = useRef('');
    const passwordRef = useRef('');
    const [rememberMe, setRememberMe] = useState(false);
    const [redirect, setRedirect] = useState(false);

    useEffect(() => {
        if (localStorage.getItem('auth')) {
            setRedirect(true);
        }
    }, [localStorage]);

    const handleSubmit = async e => {
        e.preventDefault();

        const data = {
            email: emailRef.current.value,
            password: passwordRef.current.value,
            rememberMe
        };

        try {
            const response = await axios(SERVER_URL + 'auth/login', {
                method: 'POST',
                data
            });

            const fetchData = await response.data;

            if (fetchData) {
                localStorage.setItem('auth', JSON.stringify(fetchData));
                NotificationManager.success(`Welcome, ${fetchData.nombre || 'User'}`)
                setRedirect(true);
            }
        } catch(err) {
            NotificationManager.error('Wrong user name or password', 'Wrong credentials');
            console.log('Login error:', err.message);
        }        
    };

    if (redirect) {
        return <Redirect to="/" />
    }

    return (
        <Container>
            <Row>
                <Col xl="4" lg="6" md="8" sm="10" className="mx-auto text-center form p-4">
                    <h1 className="title">Users Login</h1>
                    <Form method="POST" onSubmit={handleSubmit}>
                        <Form.Control required type="email" ref={emailRef} className="mt-2" placeholder="Email" />
                        <Form.Control required type="password" ref={passwordRef} className="mt-2" placeholder="Password" />
                        <Form.Group>
                            <Form.Check type="checkbox" value={rememberMe} onChange={() => {setRememberMe(!rememberMe)}} label="Remember me" />
                        </Form.Group>
                        <Button variant="primary" type="submit">Login</Button>
                    </Form>
                </Col>
            </Row>
        </Container>
    );
};

Regardless this does not work "really great"... I guess is not the point. I verified the React Developer Tools "Context Container" and it contains the user information inside userContext.auth.

This is my router component:

...
export default () => {
    const userContext = useContext(UserContext);

    useEffect(() => {
        if (localStorage.getItem('auth')) {
            userContext.setAuth(JSON.parse(localStorage.getItem('auth')));
        }
    }, []);

    useEffect(() => {
        if (userContext.message) {
            NotificationManager.success(userContext.message);
            userContext.setMessage(null);
        }
    }, [userContext]);

    axios.interceptors.request.use(
        config => {
            config.headers['Content-Type'] = 'application/json';
            return config;
        },
        error => Promise.reject(error)
    );

    return (
        <Router>
            {userContext.auth && (
                <div className="user">
                    <p>{userContext.auth.name} <Link to="/logout" className="btn btn-danger m-2">Log out</Link></p>

                </div>
            )}
            <Switch>
                <Route path="/login" exact>
                    <Login/>
                </Route>
                <PrivateRoute path="/" exact>
                    <Home/>
                </PrivateRoute>
                ...
                <Route path="*">
                    <Error404 />
                </Route>
            </Switch>
            <NotificationContainer />
        </Router>
    );
};

This is an usual axios request to the server:

    const totalUsers = async () => {
        try {
            const response = await axios(SERVER_URL + 'users/total');
            const result = await response.data;
            setTotalUsers(result.total);
        } catch(err) {
            NotificationManager.error('Server error while loading users. Check developer console to check them out', 'Server error');
            console.log('Error while loading users:', err.message);
        }  
    };

Should I send some headers to the server while fetching data? I didn't do that with Postman. Thanks in advance.

Maramal
  • 3,145
  • 9
  • 46
  • 90
  • 1
    By default, AJAX requests to other servers don't include cookies. You can add `{withCredentials: true}` as 2nd parameter to your axios request to send them along ([see here](https://stackoverflow.com/questions/43002444/make-axios-send-cookies-in-its-requests-automatically)) –  Mar 21 '20 at 15:46
  • I added `config.headers['Access-Control-Allow-Credentials'] = true;` to the `axios.interceptors` but nothing really changed, same error. – Maramal Mar 21 '20 at 16:07
  • Did you try to do the thing I suggested as opposed to a completely different thing? (also, adding that on the client is extremely useless, since if CORS could be circumvented l that easily, having it in the first place would be utterly useless) –  Mar 21 '20 at 17:04
  • Yes, I did try that, and it just fails due to CORS. Even when I have `app.use(cors(process.env.APP_ENV === 'development' ? {} : corsOptions));` (where `process.env.APP_ENV === 'development`. I did also try `axios.defaults.withCredentials = true`. It does not work either. – Maramal Mar 21 '20 at 17:12
  • Credentials or not, if your express server isn't set up for cors properly, you have a different problem to solve first. How is your React app supposed to store info it receives from the server if it cannot send requests to the server in the first place? Is the `app.use(cors())` line above your route setup? –  Mar 21 '20 at 17:16
  • Well, it worked by adding the exact React origin to `cors()` on the server side. Also I had to set an express middleware to send headers back with same origin and allow credentials (like its [mentioned in an answer from the link you shared](https://stackoverflow.com/a/48231372/6421288)). And also set `axios.defaults.withCredentials = true` on the frontend... With all that worked great. So make an answer and I will mark it as the correct one. Thank you. – Maramal Mar 21 '20 at 17:23
  • You don't need to use `cors()` *and* set headers manually; doing one or the other properly is enough. This is a separate question though, and this is therefore a dupe. –  Mar 21 '20 at 17:51
  • does not. Until now it worked only in the way I told before. – Maramal Mar 21 '20 at 19:27

0 Answers0