0

I am following a guide on a full-stack login/auth app. It's originally written with class components and I'm recreating it with functional components/hooks. I can successfully post requests with Postman but not via React/redux. I understand this could be a cors issue but I've tried implementing cors and it just isn't working. I've also tried adding headers to some of my axios posts with no luck either. Here's what some of it looks like right now

authActions.js -> my function for post request to register a user

...
axios.defaults.headers.post['Accept'] = 'application/json';
axios.defaults.headers.post['Content-Type'] = 'application/json';

//register user
export const registerUser = (userData, history) => (dispatch) => {
    axios.post('/api/users/register', userData).then(res => history.push('/login')).catch((err)=>{
        dispatch({
            type: GET_ERRORS,
            payload: err.response.data
        })
    });
};
...

server.js

const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
require('dotenv').config();
const passport = require('passport');
const cors = require('cors');

const users = require('./routes/api/users');

const app = express();

app.use(cors());

//body-parser middleware
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());

//connect to db
const URI = process.env.URI;
mongoose.connect(URI, {useNewUrlParser: true}).then(()=>console.log('MongoDB connected successfully')).catch(err => console.log(err));

//Passport middleware
app.use(passport.initialize());

//Passport config
require("./config/passport")(passport);

//Routes
app.use('/api/users', users);

//Print to server that its listening at given port
const port = process.env.PORT;
app.listen(port, ()=> console.log(`Server listening on port ${port}`));

users.js I've tried adding cors with different options in this post request here as well. also tried router.use(cors()) with no luck.

...
router.post('/register', (req, res)=>{
    const {errors, isValid} = validateRegisterInput(req.body);

    //check validation
    if(!isValid){
        return res.status(400).json(errors);
    }

    User.findOne({email: req.body.email}).then(user => {
        if(user){
            return res.status(400).json({email: "Email already exists"});
        } else {
            const newUser = new User({
                name: req.body.name,
                email: req.body.email,
                password: req.body.password
            });

            //hash password before saving to database
            bcrypt.genSalt(10, (err, salt)=>{
                bcrypt.hash(newUser.password, salt, (err, hash)=>{
                    if(err) throw err;
                    newUser.password = hash;
                    newUser.save().then(user=>res.json(user)).catch(err=>console.log(err));
                });
            });
        }
    });
});
...

passport.js

const jwtStrategy = require('passport-jwt').Strategy;
const extractJwt = require('passport-jwt').ExtractJwt;
const mongoose = require('mongoose');
const user = mongoose.model('User');
require('dotenv').config();

const options = {};

options.jwtFromRequest = extractJwt.fromAuthHeaderAsBearerToken();
options.secretOrKey = process.env.secretKey;

module.exports = passport => {
    passport.use(
        new jwtStrategy(options, (jwt_payload, done)=>{
            user.findById(jwt_payload.id).then(user=>{
                if(user){
                    return done(null, user);
                }
                return done(null, false);
            }).catch(err => console.log(err));
        })
    )
};

Thank you!

I think it may be the way I'm writing these functional components, I'm still learning how to do this. I think it may have something to do with my props and my store

function withRouter(Component){
    function ComponentWithRouterProp(props){
        let location = useLocation();
        let navigate = useNavigate();
        let params = useParams();
        return(
            <Component
            {...props} router={{location, navigate, params}}
            />
        );
    }
    return ComponentWithRouterProp;
}

const Register = (props) =>{
    const [state, setState] = useState({
        name: "",
        email: "",
        password: "",
        password2: "",
        errors: {}
    });

    useEffect(()=>{
        if(props.errors){
            setState({
                errors: props.errors
            })
        }
    }, [props])

    useEffect(()=>{
        if(props.auth.isAuthenticated){
            props.history.push('/dashboard');
        }
    }, [props]);

    const onChange = (e) =>{
        setState({
            ...state,
            [e.target.name]: e.target.value
        });
    }

    const submit = (e) =>{
        e.preventDefault();

        const newUser = {
            name: state.name,
            email: state.email,
            password: state.password,
            password2: state.password2
        };
        
        props.registerUser(newUser, props.history)

        console.log(newUser);
    };

    return(
        <div className='container'>
            <div className='row'>
                <div className='col s8 offset-s2'>
                    <Link to="/" className='btn-flat waves-effect'>
                        <i className='material-icons left'>keyboard_backspace</i>Back to home
                    </Link>
                    <div className='col s12'>
                        <h4>
                            <b>Register</b> below
                        </h4>
                        <p className='grey-text text-darken-1'>
                            Already have an account? <Link to="/login">Log in</Link>
                        </p>
                    </div>

                    <form noValidate onSubmit={submit}>
                        <div className='input-field col s12'>
                            <input name="name" className={classnames("", {invalid: props.errors.name})} onChange={onChange} defaultValue={state.name} error={state.errors.name} id="name" type="text" />
                            <label htmlFor='name'>Name</label>
                            <span className="red-text">{props.errors.name}</span>
                        </div>

                        <div className='input-field col s12'>
                            <input name="email" className={classnames("", {invalid: props.errors.email})} onChange={onChange} defaultValue={state.email} error={state.errors.email} id="email" type="text" />
                            <label htmlFor='email'>Email</label>
                            <span className="red-text">{props.errors.email}</span>
                        </div>

                        <div className='input-field col s12'>
                            <input name="password" className={classnames("", {invalid: props.errors.password})} onChange={onChange} defaultValue={state.password} error={state.errors.password} id="password" type="password" />
                            <label htmlFor='password'>Password</label>
                            <span className="red-text">{props.errors.password}</span>
                        </div>

                        <div className='input-field col s12'>
                            <input name="password2" className={classnames("", {invalid: props.errors.password2})} onChange={onChange} defaultValue={state.password2} error={state.errors.password2} id="password2" type="password" />
                            <label htmlFor='password2'>Confirm Password</label>
                            <span className="red-text">{props.errors.password2}</span>
                        </div>

                        <div className='col s12'>
                            <button type='submit' className='btn btn-large waves-effect waves-light hoverable blue accent-3'>Sign up</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    )
const mapStateToProps = state => ({
    auth: state.auth,
    errors: state.errors
});

export default connect(
    mapStateToProps,
    { registerUser })(withRouter(Register));
cnelson720
  • 39
  • 6
  • Why are you setting `Accept` and `Content-type` header defaults? Does your API actually use content negotiation? – Phil Aug 18 '22 at 23:33
  • just to clarify - this is happening with any post request not just api/users/register – cnelson720 Aug 18 '22 at 23:33
  • No it doesn't use content negotiation. I'm just trying what I can to get it to work. Some of the things I've found on here were to try and change those headers in axios – cnelson720 Aug 18 '22 at 23:34
  • I just added that in during my attempts to make a successful request. The only header my API actually uses is an Authorization header – cnelson720 Aug 18 '22 at 23:37
  • I'd just like to know why **everyone** seems to do this when it's simply [not required](https://stackoverflow.com/a/73017821/283366) – Phil Aug 18 '22 at 23:38
  • I'm not sure. That seemed to be a common response to the issue I'm having while I've been poking around. Thanks for clearing that up for me though – cnelson720 Aug 18 '22 at 23:40
  • Doesn't look like CORS is even involved here. You've registered some Passport middleware for all routes. What does it do? How have you configured Passport? – Phil Aug 18 '22 at 23:43
  • I completely forgot about passport. I’ll have to look at that. @jub0bs I’m not getting any cors errors just that when attempt a request it says 403 forbidden in my clients console – cnelson720 Aug 18 '22 at 23:57
  • You _"forgot about passport"_? What debugging have you done? A `403 Forbidden` response says there's something wrong with your authentication so anything to do with verifying authentication should have been your very first stop – Phil Aug 19 '22 at 00:03
  • Does this answer your question? [Passportjs exclude resources](https://stackoverflow.com/questions/49880682/passportjs-exclude-resources) – Phil Aug 19 '22 at 00:06
  • I didn't know that `403 Forbidden` had to do with authentication. Thanks. I tried passing passport.authenticate() middleware into my router and I'm still getting 403. – cnelson720 Aug 19 '22 at 00:56
  • I think it's more you _don't_ want to add the middleware for certain routes like `users/register`. See the post I linked earlier for how to do that – Phil Aug 19 '22 at 01:00
  • [Passport docs](https://www.passportjs.org/concepts/authentication/middleware/) I've tried reading through these docs and I attempted to add this into my post request with no luck. I'm having trouble understanding why you are saying I shouldn't add middleware for this but most things I'm finding, to me, seem like I should be adding middleware – cnelson720 Aug 19 '22 at 01:21
  • You can't validate authentication when you don't even have a user account. I assume you use `/register` to create one. Similarly, if you have a `/login` route, you wouldn't want the passport middleware to apply to that one either. Basically any handler for routes that won't have an `Authorization` request header should be exempt from the middleware – Phil Aug 19 '22 at 01:24
  • I do have user accounts in my database, I've been able to post /register to create one with postman. I'm getting 403 forbidden on both /register and /login. – cnelson720 Aug 19 '22 at 01:26
  • I feel you're very close to having a lightbulb moment but just take a step back. When the client makes requests to either `/register` or `/login`, it will **not** have an `Authorization` header with the JWT, correct? Your middleware verifies that the JWT from request headers is valid, correct? With that in mind, you don't want it acting on the `/register` or `/login` routes because they won't have the request header – Phil Aug 19 '22 at 01:29
  • 1
    To really spell it out... `app.use(passport.initialize())` adds the JWT verification middleware to **every route in the entire application**. You should configure different routers for routes that should be authenticated and apply the middleware to those only – Phil Aug 19 '22 at 01:37
  • I'm no longer getting the 403 forbidden. I'm going to update my post. – cnelson720 Aug 19 '22 at 01:49
  • Hey I really appreciate your time. I'm going to walk away from this for now and when I come back I'm going to look into configuring routers for my /register and /login so it only authenticates those routes. I think I also need to re look at the way I've written the components and my implementation of redux store – cnelson720 Aug 19 '22 at 01:58

1 Answers1

0

I was getting a 403 Forbidden error because my proxy wasn't set up properly. I initially had it set up for port 5000 in my client's package.json proxy but when I had run my server on 5000, it told me I already had 5000 in use. I changed it in my .env file but not in my proxy settings in my client's package.json. Once I changed it to match my server I was able to successfully post

cnelson720
  • 39
  • 6