POST http://localhost:3000/api/users 404 (Not Found) - reactjs

MERN Stack/REDUX -- Attempting to send user data for signup/registration from my React app to express and into my mongoDB but keep receiving a 404 error in letting me know that the page doesnt exist. I've been able to connect it before on a previous application but having trouble now.
Server.js file:
const express = require("express");
const connectDB = require("./config/db");
const path = require("path");
const bodyParser = require("body-parser");
const app = express();
//Connecting to the DB \\
connectDB();
// Middleware Init\\
app.use(express.json({ extended: false }));
app.use(bodyParser.json());
// Routes \\
app.use("/api/users", require("./routes/users"));
app.use("/api/auth", require("./routes/auth"));
// Static assets if in production (deployment purposes) \\
if (process.env.NODE_ENV === "production") {
// Set static folder \\
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
}
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Users Route:
const express = require("express");
const router = express.Router();
const { check, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const config = require("config");
const auth = require("../middleware/auth");
//importing User Schema \\
const User = require("../models/User");
// #route POST api/users
// #desc Register user
// #access Public
router.post(
"/", [
check("firstname", "First Name is Required").not().isEmpty(),
check("lastname", "Last Name is Required").not().isEmpty(),
check("email", "Email is Required").isEmail(),
check(
"password",
"Please enter a password with 5 or more character"
).isLength({ min: 5 }),
],
async(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// just deconstruct firstname, lastname, email, and password, isAdmin from req.body \\
const { firstname, lastname, email, password, isAdmin } = req.body;
try {
// see if user exists \\
let user = await User.findOne({ email });
if (user) {
return res
.status(400)
.json({ errors: [{ msg: "User already exists" }] });
}
user = new User({
firstname,
lastname,
email,
password,
isAdmin,
});
// Encrypt password \\
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
// Login good for 1 hour \\
jwt.sign(
payload,
config.get("jwtSecret"), { expiresIn: 36000 },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
// #route PUT api/users/address
// #desc Add/edit address
// #access Private
router.put(
"/address", [
auth, [
check("street", "Street is required").not().isEmpty(),
check("country", "Country is required").not().isEmpty(),
check("city", "City is required").not().isEmpty(),
check("state", "State/Province is required").not().isEmpty(),
check("zipcode", "Zip/Postal Code is required").not().isEmpty(),
],
],
async(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { street, country, city, state, zipcode } = req.body;
const newAddress = {
street,
country,
city,
state,
zipcode,
};
try {
// look for the user with the id and then update the address
await User.updateOne({ _id: req.user.id }, {
$set: {
address: newAddress,
},
});
res.json({ msg: "Successfully saved your address." });
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
module.exports = router;
DB Configuration(db.js):
const mongoose = require("mongoose");
const config = require("config");
const db = config.get("mongoURI");
const connectDB = async() => {
try {
await mongoose.connect(db, {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false,
useUnifiedTopology: true,
});
console.log("MongoDB Connected!..");
} catch (err) {
console.error(err.message);
//exit process with failure\\
process.exit(1);
}
};
module.exports = connectDB;
DB configuration(default.json)
{
"mongoURI": "mongodb+srv://AJ****:*********#sustdropclust.zcc0v.mongodb.net/*******?
retryWrites=true&w=majority",
"jwtSecret": "**********"
}
Middleware:
const jwt = require("jsonwebtoken");
const config = require("config");
module.exports = function(req, res, next) {
// Get token from header \\
const token = req.header("x-auth-token");
// check if no token \\
if (!token) {
return res.status(401).json({ msg: "No token, auth denied" });
}
// Verify token \\
try {
const decoded = jwt.verify(token, config.get("jwtSecret"));
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({ msg: "Token not valid" });
}
};
Redux Actions(auth.js):
import axios from "axios";
import { setAlert } from "./alert";
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
MAKE_ADMIN,
RESET_RECENT_ORDERS_LOADING,
} from "./types";
import setAuthToken from "../utilities/setAuthToken";
// Load User
export const loadUser = () => async(dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
if (res.data.isAdmin) {
dispatch({ type: MAKE_ADMIN });
}
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
// Register User
export const register = ({ firstname, lastname, email, password }) => async(
dispatch
) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ firstname, lastname, email, password });
try {
const res = await axios.post("/api/users", body, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
dispatch(setAlert("Registration is successful", "success"));
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: REGISTER_FAIL,
});
}
};
// Login User
export const login = (email, password) => async(dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post("/api/auth", body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch(setAlert("Welcome! Login Successful!", "success"));
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: LOGIN_FAIL,
});
}
};
// Logout / Clear Profile
export const logout = () => (dispatch) => {
dispatch({ type: LOGOUT });
// Resets the loading for recent orders incase another user logs in without refreshing the page
dispatch({ type: RESET_RECENT_ORDERS_LOADING });
};
// Add or Edit Address - Add address if it is null, edit it otherwise.
export const addAddress = ({ formData }) => async(dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
const res = await axios.put("/api/users/address", formData, config);
dispatch(loadUser());
dispatch(setAlert(res.data.msg, "success"));
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
}
}
};
Redux Reducers(auth.js):
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
MAKE_ADMIN,
} from "../actions/types";
//add isAdmin later to authenticate if the current user is an admin (maybe just check user.isAdmin
once we get the user and put it in the state)
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: false,
loading: true,
user: null,
isAdmin: false,
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: payload,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem("token", payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_FAIL:
case LOGIN_FAIL:
case AUTH_ERROR:
case LOGOUT:
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
isAdmin: false,
user: null,
};
case MAKE_ADMIN:
return {
...state,
isAdmin: true,
};
default:
return state;
}
}
Registration page(register.js):
import React, { useState } from "react";
import { Link, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import { setAlert } from "../../actions/alert";
import { register } from "../../actions/auth";
import PropTypes from "prop-types";
import "./Login.css";
const Register = ({ setAlert, register, isAuthenticated }) => {
const [formData, setFormData] = useState({
firstname: "",
lastname: "",
email: "",
password: "",
password2: "",
});
const { firstname, lastname, email, password, password2 } = formData;
// it changes the value of the target every keystroke
const onChange = (e) =>
setFormData({...formData, [e.target.name]: e.target.value });
const onSubmit = (e) => {
e.preventDefault();
if (password !== password2) {
setAlert("Passwords do not match", "danger");
} else {
register({ firstname, lastname, email, password });
}
};
// Redirects to /account after a successful registration
if (isAuthenticated) {
return <Redirect to = "/account" / > ;
}
return (
<div className = "wrapper-register" >
<h1 className = "large text-dark" > create an account < /h1>
<form onSubmit = {(e) => onSubmit(e)}>
<div className = "form-group" >
<label htmlFor = "firstname" > first name < /label>
<input className = "form-control" type = "text" name = "firstname" value = { firstname } onChange = {(e) => onChange(e)}/>
</div>
<div className = "form-group" >
<label htmlFor = "lastname" > last name </label>
<input className = "form-control" type = "text" name = "lastname" value = { lastname } onChange = {(e) => onChange(e)}/>
</div>
<div className = "form-group" >
<label htmlFor = "email" > email </label>
<input className = "form-control" type = "email" name = "email" value = { email } onChange = {(e) => onChange(e)}/>
</div>
<div className = "form-group" >
<label htmlFor = "password" > password </label>
<input className = "form-control" type = "password" name = "password" value = { password } onChange = {(e) => onChange(e)} minLength = "6" />
</div>
<div className = "form-group" >
<label htmlFor = "password" > confirm password < /label> < input className = "form-control" type = "password" name = "password2" value = { password2 } onChange = {(e) => onChange(e)}minLength = "6" />
</div>
<div className = "register-button" >
<input type = "submit" className = "btn btn-dark btn-block" value = "Register" />
</div>
</form >
<p>Already have an account ? < Link to = "/account/login" > Sign In </Link> </p>
</div>
);
};
Register.propTypes = {
setAlert: PropTypes.func.isRequired,
register: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(mapStateToProps, { setAlert, register })(Register);

I noticed that you are running the server on port 5000, i guess that might have caused the error since you are sending a POST request to port 3000 instead. Unless you specified a port in process.env.PORT of 3000.

Figured it out. When you create a react app through the create-react-app cli you have to specify and set your proxy in package.json like so "proxy": "http://localhost:4000". This way, when you fetch('/api/signup') in development, the development server will recognize that it’s not a static asset, and will proxy your request to http://localhost:4000/api/signup

in Redux Actions(auth.js) you can set base URL path in axios defaults (just after imports).
window.axios.defaults.baseURL = (process.env.NODE_ENV !== 'production') ? 'your production server url' : 'http://localhost:5000/';

Restart your frontend. If incase you are using react, just stop it from running and re run again! Problem solved!!!

Related

Why can't I store my session in Express.js (MERN Stack)?

I've been trying to store a user's email in the session for my express.js file. But everytime I try something, and call another function, the session remains undefined. Been working on this for weeks now, and I can't seem to find the answer.
server.js file:
import express from 'express';
import cookieParser from 'cookie-parser';
import session from 'express-session';
const app = express();
app.use(cookieParser());
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
maxAge : 1000 * 60 * 60 * 3, // if 1 day * 24 but since *3 its for 3 hours only
},
}))
app.post('/user/login', (req, res) => {
const loginUser = req.body;
const email = loginUser['email'];
const password = loginUser['password'];
if (!email || !password){
res.status(400).json({success: false, error: "Please provide email and password"});
}
try {
Users.findOne({ email: email }, (err, user) => {
if (password == user['password']){
req.session.user(user['email']);
res.status(200).send(user_email);
} else {
res.status(400).json({success: false, error: "incorrect password"});
}
});
} catch {
}
})
Calling the Login file from the frontend (react js)
import React, { useState } from 'react';
import { Grid, TextField, Button } from '#mui/material';
import "./SignUpLogin.css";
import axios from '../../axios';
import useForm from './useForm';
import { Form } from './useForm';
const initialValues = {
email: '',
password: ''
}
function Login({ modalFunc }) {
const LoginUser = e => {
console.log("INSIDE LOGIN USER");
modalFunc();
e.preventDefault();
axios.post('/user/login', values, {withCredentials: true})
.then(response => {
console.log("in login user");
console.log(response.data);
})
}
const {
values,
setValues,
handleInputChange
} = useForm(initialValues);
return (
<div className="Login">
<Form>
<Grid item>
<TextField
required
variant="outlined"
label="Email"
name="email"
color="secondary"
fullWidth
value={ values.email }
onChange={ handleInputChange }
/>
</Grid>
<Grid item>
<TextField
required
variant="outlined"
label="Password"
name="password"
type="password"
color="secondary"
fullWidth
value={ values.password }
onChange={ handleInputChange }
/>
</Grid>
<Grid item>
<Button
variant="contained"
fullWidth
onClick = { LoginUser }>
Login
</Button>
</Grid>
</Form>
</div>
)
}
export default Login
But when I call server.js again in another get function, session is undefined.
app.get('/user/loggedIn', (req, res) => {
console.log(req.session.user);
user_email = req.session.user;
if (req.session.user) {
Users.findOne({ email: user_email }, (err, user) => {
// console.log("in logged in, in server!!!");
// console.log(user);
res.status(200).send(user);
})
} else {
console.log("no session");
res.status(400);
}
})
Calling app.get('/user/loggedIn') in react.js file:
function Header() {
const [modalOpen, setModalOpen] = useState(false);
const changeModal = () => {
setModalOpen(!modalOpen)
}
const [user, setUser] = useState(null);
useEffect(() => {
// axios.get('/user/loggedIn', {}, {withCredentials: true})
axios.get('/user/loggedIn', {}, {withCredentials: true})
.then(response => {
// console.log("RESPONSE FROM LOGGED IN");
// console.log(response.data);
setUser(response.data);
})
})
Here is my working solution,
const bodyParser = require('body-parser');
const MongoStore = require('connect-mongo');
const cors = require('cors');
const express = require('express');
const { connect } = require('mongoose');
const UserModel = require('./models/UserMode');
const session = require('express-session');
(async () => {
try {
let response = await connect('mongodb://localhost:27017/test');
console.log('connected');
} catch (error) {
console.log(error);
}
})();
const app = express();
app.all('/*', function (req, res, next) {
res.header('Access-Control-Allow-Credentials', true); //this is you are missing
next();
});
const corsOpts = {
origin: ['http://localhost:3001'], // you have to provide list of whitelisted urls. In this case url of your frontend react app.
//you cant use * because you are using {withCredentials: true} in axios
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type'],
};
app.use(cors(corsOpts)); //this is you are missing
app.use(bodyParser.json());
app.use(
session({
cookie: { secure: false, httpOnly: false },
secret: 'Your_Secret_Key',
resave: true,
saveUninitialized: true,
name: 'some name',
store: MongoStore.create({
mongoUrl: 'mongodb://localhost:27017/test', // you have to provide some storage to store session data
}),
})
);
app.get('/login_check', async (req, res) => {
console.log(req.session); // just check if session is getting stored or not
res.json('check');
});
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
let userDetails = await UserModel.findOne({ email, password });
req.session.user = userDetails; // here pass any details that you want to send to session
console.log(userDetails);
res.send(userDetails);
} catch (error) {
console.log(error);
res.status(500);
}
});
const server = app.listen(5000, () =>
console.log(`Server started on port ${5000}`)
);
Also couple of things that you are doing wrong, after this line
res.status(400).json({success: false, error: "Please provide email and password"});
you should write it this way, or else it would throw an error.
return res.status(400).json({success: false, error: "Please provide email and password"});
// or this way
res.status(400).json({success: false, error: "Please provide email and password"});
return;
Here is the clean solution:
first you've to import 2 packages and add those line of code in server.js file:
const session = require('express-session');
const MongoSessionStore = require('connect-mongodb-session')(session);
const MongoDBStore = new MongoSessionStore({
uri: process.env.MONGODB_URL,
collection: 'sessions',
});
app.use(
session({
secret: 'my-secret',
resave: false,
saveUninitialized: false,
store: MongoDBStore,
})
);
then store the session in this route:
app.post('/user/login', (req, res) => {
req.session.user = user;
}

How to return api errors to Login Component in NextAuth.js

How to return API errors to Login Component in NextAuth.js.Actually, I am trying to pass the Errors back to Login Component in NextAuth(Credentials Provider). I am getting this object in console error: "CredentialsSignin" ok: false status: 401 url: null [[Prototype]]: Object Everything is working fine like I am able to log in, but when I am trying to handle errors coming from APIs, I am unable to handle them.
[...nextauth.js] File
export default (req, res) =>
NextAuth(req, res, {
providers: [
CredentialsProvider({
authorize: async (credentials) => {
try {
const data = {
email: credentials.email,
password: credentials.password
}
const user = await login(data);
console.log("401 Error",user.data);
if (user.data.status==200) {
console.log("200 data",user.data);
return Promise.resolve(user.data);
}else if(user.data.status==401){
// Here I wants to Handle Errors and return them back to My login Compenent
}
} catch (error) {
if (error.response) {
console.log(error.response);
Promise.reject(new Error('Invalid Username and Password combination'));
}
}
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
pages: {
signIn: '/login',
},
callbacks: {
jwt: async ({token,user})=>{
if(user){
token.userid = user.id;
token.name = user.username;
token.token = user.token;
}
return token;
},
session: (session,token)=>{
return session;
}
},
secret:"test",
jwt:{
secret:"test",
encryption:true,
},
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
jwt: true,
maxAge: 1 * 3 * 60 * 60, // 3 hrs
updateAge: 24 * 60 * 60, // 24 hours
}
});
const login = async data => {
var config = {
headers: {
'Content-Type': "application/json; charset=utf-8",
'corsOrigin': '*',
"Access-Control-Allow-Origin": "*"
}
};
const url = 'api/auth/login';
const result = await axios.post(url,data,config);
return result;
};
Login Components
const LoginSubmit = async (event) => {
event.preventDefault();
const enteredEmail = inputText.email;
const enteredPassword = inputText.password;
// console.log(enteredEmail);
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("Final Result",result);
};
Yes you can do it :
Try :
// component.js
const res = await signIn("credentials", {
email: inputs.email,
password: inputs.password,
callbackUrl: `/`,
redirect: false,
});
if (res?.error) {
setLoading(false);
Swal.fire("Invalid Login", "username or password is incorrect");
}
and in Nextauth file you must throw an error if a user entred invalid credential!
//[...nextauth].js
const providers = [
Providers.Credentials({
name: "credentials",
authorize: async (credentials) => {
//statr try
try {
const user = await
axios.post(`${API_URL}/auth/local`, {
identifier: credentials.email,
password: credentials.password,
});
if (user) {
return { status: "success",
data:user.data };
}
} catch (e) {
throw new Error("Not found ");
}
},
}),
];

Get user info with JWT in React

I´m storing the user token in the localstorage after a user logs in, and i would like to know how i can use this token that is in the localstorage to fetch the user info.
backend login.js
module.exports.login = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email: email });
if (!user) return res.status(404).json({ message: "Usuário não cadastrado!" });
const isMatch = bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ message: "Email e/ou senha incorretos." });
const token = tokenGenereator(user._id);
return res.status(200).json({ result: user, token });
} catch (error) {
console.log(error)
}
};
frontend login.js
export const login = (data, history) => {
return async (dispatch) => {
try {
const userData = await api.login(data);
localStorage.setItem("userToken", JSON.stringify(userData.data.token));
dispatch(
setUser({
fullname: userData.data.fullname,
email: userData.data.email,
isAuth: true,
})
);
history.push("/home");
} catch (error) {
if (error.response && error.response.data.message) {
dispatch(
setNotification({
variant: "danger",
message: error.response.data.message,
})
);
}
}
};
};
I pass the token to the backend
// i would pass the jwt to the function as a parameter
export const fetchUser = (data) => {
return async (dispatch) => {
const user = await api.getUserData(data);
console.log(user);
};
};
And i recieve
module.exports.getCurrentUser = async (req, res) => {
const { token } = req.body;
// i dont know what to do from here...
};

React + Express sending data?

I'm working on a login/register system with React front-end and Node/Express back-end.
When the registration fails, i want to reload the registration page and return the error msgs to my react front end.
Instead of refreshing the page, it renders the JSON data.
How can you go about re-rendering the registration form, while sending the data behind the scenes?
router.post("/register", (req, res) => {
const { name, email, password, password2 } = req.body;
let errors = [];
...
...
...
if (errors.length > 0) {
res.send({
errors,
name,
email,
password,
password2
});
You need to receive the response from express in your react app, and perform your logic inside your submit function in react.
One way you can do this is to create a state variable for the error message. Make the initial value null or ''. When user registration fails catch the error and assign it your error variable in state. Here is an example of user registration system:
//RegistrationForm.js
import React, { useState } from 'react';
import AuthApiService from '../../services/auth-api-service';
import { Input } from '../Utils/Utils';
import { Button } from '#material-ui/core';
import './RegistrationForm.css';
export default function RegistrationForm(props) {
const [error, setError] = useState(null)
const errorDiv = error
? <div className="error">
<i class="material-icons error-icon">error_outline</i>
{error}
</div>
: '';
const handleSubmit = e => {
e.preventDefault();
setError(null);
const { full_name, user_name, password } = e.target;
AuthApiService.postUser({
full_name: full_name.value,
user_name: user_name.value,
password: password.value
})
.then(user => {
full_name.value = '';
user_name.value = '';
password.value = '';
props.onRegistrationSuccess();
})
.catch(res => {
setError(res.error);
})
};
return(
<form className='RegistrationForm'
onSubmit={handleSubmit}
>
<div className='full_name'>
<label htmlFor='RegistrationForm__full_name'>
Full name
</label>
<Input
name='full_name'
type='text'
required
id='RegistrationForm__full_name'>
</Input>
</div>
<div className='user_name'>
<label htmlFor='RegistrationForm__user_name'>
User name
</label>
<Input
name='user_name'
type='text'
required
id='RegistrationForm__user_name'>
</Input>
</div>
<div className='password'>
<label htmlFor='RegistrationForm__password'>
Password
</label>
<Input
name='password'
type='password'
required
id='RegistrationForm__password'
>
</Input>
</div>
<div className='confirm-password'>
<label htmlFor="LoginForm__confirm-password">
Retype Password
</label>
<Input
name='confirm-password'
type="password"
required
id="LoginForm__confirm-password">
</Input>
</div>
{errorDiv}
<Button type='submit' variant='contained' color='default'>
Register
</Button>
</form>
)
}
//AuthApiService.js
import config from '../config';
import TokenService from './token-service';
import IdleService from './idle-service';
const AuthApiService = {
postUser(user) {
return fetch(`${config.API_ENDPOINT}/users`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(user)
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
IdleService.registerIdleTimerResets()
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
},
postLogin({user_name, password }) {
return fetch(`${config.API_ENDPOINT}/auth/login`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({user_name, password}),
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
},
postGuestLogin() {
return fetch(`${config.API_ENDPOINT}/auth/guest-login`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
},
postRefreshToken() {
return fetch(`${config.API_ENDPOINT}/auth/refresh`, {
method: 'POST',
headers: {
'authorization': `Bearer ${TokenService.getAuthToken()}`,
},
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
.catch(err => {
TokenService.clearAuthToken()
console.log('refresh token request error')
console.log(err)
})
}
}
export default AuthApiService;
//TokenService.js
import jwtDecode from 'jwt-decode';
import config from '../config';
let _timeoutId;
const _TEN_SECONDS_IN_MS = 10000;
const TokenService = {
saveAuthToken(token) {
window.localStorage.setItem(config.TOKEN_KEY, token);
},
getAuthToken() {
return window.localStorage.getItem(config.TOKEN_KEY);
},
clearAuthToken() {
window.localStorage.removeItem(config.TOKEN_KEY);
},
hasAuthToken() {
return !!TokenService.getAuthToken()
},
makeBasicAuthToken(userName, password) {
return window.btoa(`${userName}:${password}`);
},
parseJwt(jwt) {
return jwtDecode(jwt);
},
readJwtToken() {
return TokenService.parseJwt(TokenService.getAuthToken());
},
_getMsUntilExpiry(payload) {
return (payload.exp * 1000) - Date.now();
},
queueCallbackBeforeExpiry(callback) {
const msUntilExpiry = TokenService._getMsUntilExpiry(
TokenService.readJwtToken()
);
_timeoutId = setTimeout(callback, msUntilExpiry - _TEN_SECONDS_IN_MS);
},
clearCallbackBeforeExpiry() {
clearTimeout(_timeoutId);
}
};
export default TokenService;
EDIT:
Here is an example of what the router can look like:
//users-router.js
const express = require('express')
const path = require('path')
const UsersService = require('./users-service')
const AuthService = require('../auth/auth-service')
const usersRouter = express.Router()
const jsonBodyParser = express.json()
usersRouter.post('/', jsonBodyParser, (req, res, next) => {
const { password, user_name, full_name } = req.body
for (const field of ['password', 'full_name', 'user_name'])
if (!req.body[field])
return res.status(400).json({
error: `Missing ${field} in request body`
})
const passwordError = UsersService.validatePassword(password)
if (passwordError)
return res.status(400).json({ error: passwordError })
UsersService.hasUserWithUserName(
req.app.get('db'),
user_name
)
.then(hasUserWithUserName => {
if(hasUserWithUserName)
return res.status(400).json({ error: 'Username already taken' })
return UsersService.hashPassword(password)
.then(hashedPassword => {
const newUser = {
user_name,
full_name,
password: hashedPassword,
date_created: 'now()'
}
return UsersService.insertUser(
req.app.get('db'),
newUser
)
.then(user => {
const serializedUser = UsersService.serializeUser(user)
const { user_name, user_id } = serializedUser
const sub = user_name
const payload = {user_id: user_id}
res
.status(201)
.location(path.posix.join(req.originalUrl, `/${user.id}`))
.send({
authToken: AuthService.createJwt(sub, payload),
user: serializedUser
})
})
})
})
.catch(next)
})
module.exports = usersRouter
//users-service.js
const bcrypt = require('bcryptjs')
const xss = require('xss')
const REGEX_UPPER_LOWER_NUMBER_SPECIAL = /(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!##\$%\^&])[\S]+/
const UsersService = {
hasUserWithUserName(db, user_name) {
return db('askify_users')
.where({ user_name })
.first()
.then(user => !!user)
},
insertUser(db, newUser) {
return db
.insert(newUser)
.into('askify_users')
.returning('*')
.then(([user]) => user)
},
validatePassword(password) {
if (password.length < 8) {
return 'Password must be longer than 8 characters'
}
if (password.length > 72) {
return 'Password must be less than 72 characters'
}
if (password.startsWith(' ') || password.endsWith(' ')) {
return 'Password must not start or end with empty spaces'
}
if (!REGEX_UPPER_LOWER_NUMBER_SPECIAL.test(password)) {
return 'Password must contain one upper case, lower case, number and special character'
}
return null
},
hashPassword(password) {
return bcrypt.hash(password, 12)
},
serializeUser(user) {
return {
id: user.id,
full_name: xss(user.full_name),
user_name: xss(user.user_name),
nickname: xss(user.nick_name),
date_created: new Date(user.date_created),
}
},
}
module.exports = UsersService
// auth-service.js
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const config = require('../config')
const AuthService = {
getUserWithUserName(db, user_name) {
return db('askify_users')
.where({user_name})
.first()
},
comparePasswords(password, hash) {
return bcrypt.compare(password, hash)
},
createJwt(subject, payload) {
return jwt.sign(payload, config.JWT_SECRET, {
subject,
expiresIn: config.JWT_EXPIRY,
algorithm: 'HS256',
})
},
verifyJwt(token) {
return jwt.verify(token, config.JWT_SECRET, {
algorithms: ['HS256'],
})
},
parseBasicToken(token) {
return Buffer
.from(token, 'base64')
.toString()
.split(':')
},
}
module.exports = AuthService

react & redux with hooks: Actions must be plain objects. Use custom middleware for async actions

i tried looking for similar answers to help solve my problem but i couldn't find anything using react redux hooks. This code was from a tutorial and originally written using the Context api. I wanted to trying using it with react-redux-hooks, but i got stuck. Basically i'm trying to register a user with a name, email and password, then pass these three as an object to the express server which will validated it and give me back a jwt token. Then come back to the client side and send the token to the reducer, which adds the token to localstorage and sets the state to isAuthenticated. The error i get is on the dispatch.
Dispatch
const onSubmit = e => {
e.preventDefault();
if (name === "" || email === "" || password === "") {
dispatch(setAlert("Please enter all fields", "danger"));
} else if (password !== password2) {
dispatch(setAlert("Passwords do not match", "danger"));
} else {
dispatch(register({ name, email, password })); // Error is here
}
setTimeout(() => {
dispatch(removeAlert());
}, 5000);
};
Action
export const register = async formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
try {
const res = await axios.post("/api/users", formData, config);
return {
type: "REGISTER_SUCCESS",
payload: res.data
};
} catch (err) {
return {
type: "REGISTER_FAIL",
payload: err.response.data.msg
};
}
};
Reducer
const authReducer = (
state = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null,
error: null
},
action
) => {
switch (action.type) {
case "REGISTER_SUCCESS":
console.log("register success");
localStorage.setItem("token", action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false
};
case "REGISTER_FAIL":
console.log("register failed");
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
error: action.payload
};
default:
return state;
}
};
Store
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|| compose;
const store = createStore(
allReducers,
composeEnhancers(applyMiddleware(thunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Express server
router.post(
"/",
[
check("name", "Please a name")
.not()
.isEmpty(),
check("email", "Please include a valid email").isEmail(),
check(
"password",
"Please enter a password with 6 or more characters"
).isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { name, email, password } = req.body;
try {
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({
msg: "User already exists"
});
}
user = new User({
name,
email,
password
});
// hash passsword
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{
expiresIn: 360000
},
(err, token) => {
if (err) throw err;
res.json({
token
});
}
);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
I believe this question has answers to the issue your experiencing here: how to async/await redux-thunk actions?
Using this example, it may look something like this (wasn't able to test it):
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
const request = axios.post("/api/users", formData, config);
return dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
request.then(onSuccess, onError);
};
};
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
return async dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
try {
const success = await axios.post("/api/users", formData, config);
return onSuccess(success);
} catch (error) {
return onError(error);
}
}
};

Resources