How do I implement login functionality in a MERN app? - node.js

I am developing a (simple-to-use) note-taking web application on my localhost (port 3000 for frontend, port 5000 for the backend). I'm having trouble implementing the user login functionality. I'll try to distill the problem here.
My main App.jsx component has a LoginScreen.jsx component as a child, which is here:
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import AuthService from '../services/auth.js';
import './LoginScreen.css';
const LoginScreen = ({ history }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
useEffect(() => {
if (localStorage.getItem('authToken')) history.push('/');
}, [history]);
const loginHandler = async e => {
e.preventDefault();
try {
const data = { email, password };
AuthService.loginUser(data).then(response => {
localStorage.setItem('authToken', response.data.token);
});
history.push('/');
} catch (error) {
setError(error.response.data.error);
setTimeout(() => {
setError('');
}, 5000);
}
};
return (
<div className="login-screen">
<form
className="login-screen__form"
onSubmit={loginHandler}
autoComplete="off"
>
<h3 className="login-screen__title">Login</h3>
{error && <span className="error-message">{error}</span>}
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
type="email"
required
id="email"
placeholder="enter email"
value={email}
onChange={e => setEmail(e.target.value)}
tabIndex={1}
/>
</div>
<div className="form-group">
<label htmlFor="password">
Password:{' '}
<Link
to="/forgotpassword"
className="login-screen__forgotpassword"
tabIndex={4}
>
Forgot Password?
</Link>
</label>
<input
type="password"
required
id="password"
placeholder="enter password"
value={password}
onChange={e => setPassword(e.target.value)}
tabIndex={2}
/>
</div>
<button type="submit" className="btn btn-primary" tabIndex={3}>
Login
</button>
<span className="login-screen__subtext">
Don't have an account? <Link to="/register">Register</Link>
</span>
</form>
</div>
);
};
export default LoginScreen;
When the user clicks "Login," that should trigger the AuthService, shown here:
import http from '../routing/http-auth.js';
class AuthService {
loginUser = data => http.post('/login', data);
registerUser = data => http.post('/register', data);
userForgotPassword = data => http.post('/forgotpassword', data);
userPasswordReset = data => http.put('/passwordreset/:resetToken', data);
}
export default new AuthService();
The http in the above file is an axios instance, shown below.
import axios from 'axios';
export default axios.create({
baseURL: 'http://localhost:5000/api/v1/auth',
headers: {
'Content-type': 'application/json'
}
});
So, the request is routed to the backend, where it goes here:
import express from 'express';
import AuthController from './auth.controller.js';
const router = express.Router();
router.route('/register').post(AuthController.register);
router.route('/login').post(AuthController.login);
router.route('/forgotpassword').post(AuthController.forgotPassword);
router.route('/passwordreset/:resetToken').put(AuthController.passwordReset);
export default router;
And then here:
static login = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
if (!email || !password) {
return next(
new ErrorResponse('please provide an email and password', 400)
);
}
try {
const user = await User.findOne({ email }).select('+password');
if (!user) return next(new ErrorResponse('invalid credentials', 401));
const isMatch = await user.matchPasswords(password);
if (!isMatch) return next(new ErrorResponse('invalid credentials', 401));
sendToken(user, 200, res);
} catch (error) {
next(error);
}
};
This all worked on a dummy application, but now, it's not working in my note-taking app. I get the error:
Uncaught (in promise) TypeError: Cannot read property 'data' of undefined
at loginHandler (LoginScreen.jsx:27)
How can I make this work?

Your TypeError: Cannot read property 'data' of undefined means that your code tried to read the .data property from a undefined (null, missing) object.
At line 27 your code does setError(error.response.data.error); Therefore your error message means error exists but error.response does not.
Put console.log(error) right before that line 27 (or set a breakpoint) so you can make sure you understand the error object you catch.
It would be nice if the error message said TypeError: Cannot read property 'data' of an undefined object named error.response. But it doesn't so you must make that inference yourself.

Related

I have coded login page in react as well as login API

Having problem with login. The login code I have done doesn't authenticate the username and password(it does but there is problem I have described it ahead) I can access the database dashboard without I even logging in like (localhost:3000/ URL used for registration, localhost:3000/login URL used for login, localhost:3000/read used for data stored in database), so even if I am on registration page or login page I can access the localhost:3000/read without logging in which should not happen but still it does. So I want to stop the user from accessing the database dashboard without logging in.
Login.js
import React, { useState } from 'react'
import { Button, Form } from 'semantic-ui-react'
import axios from 'axios'
import { useNavigate } from 'react-router'
export default function Login() {
let navigate = useNavigate()
const [username, setusername] = useState('');
const [Employee_password, setEmployee_password] = useState('');
const GetData = (e) => {
e.preventDefault();
console.log(username, Employee_password)
axios.post('http://localhost:5000/emp/login', {
username,
Employee_password
}).then((res) => {
alert('Login Successfull')
navigate('/read')
}).catch(err => {
console.log(err)
})
}
return (
<div>
<Form className='create-form' onClick={GetData}>
<h2>Login into existing ID</h2>
<Form.Field>
<label>Enter Username</label>
<input type='text' placeholder='Username' onChange={(e) => setusername(e.target.value)}></input>
</Form.Field>
<Form.Field>
<label>Password</label>
<input type='password' placeholder='Password' onChange={(e) => setEmployee_password(e.target.value)}></input>
</Form.Field>
<Button type='submit'>Login</Button>
</Form>
</div>
)
}
API code
app.post('/emp/login',(req,res) => {
pool.getConnection((err,connection) =>{
const username = req.body.username;
const Employee_password = req.body.Employee_password;
connection.query('SELECT* FROM employee WHERE username =? AND Employee_password =?', [username, Employee_password], (error,results) =>{
if(err){
console.log('Error query' +err);
return res.status(500).json({msg: 'Error query'});
}
if(results.length === 0){
return res.status(401).json({msg: 'Ivalid credentials'});
}
return res.status(200).json({msg: 'Login successfull'});
})
})
})

Redirects to wrong page when logging in

it redirects to an empty js file when logging in, not to the js file I wrote. Should I redirect in my database code?
////////////////
const client = require('../../db')
const express = require('express');
const app = express();
const cors = require("cors");
app.use(cors());
app.use(express.json()); //req.body
app.listen(2136, ()=>{
console.log("Sever is now listening at port 5000");
})
client.connect();
app.post("/login", async (req, res) => {
try {
const { email, password } = req.body;
const user = await client.query(
`SELECT * FROM users WHERE email=$1 AND password=$2`,
[email, password]
);
if (user.rows.length === 0) {
res.status(404).send("Kullanıcı adı veya şifre yanlış");
} else {
res.send("Kullanıcı adı veya şifre doğru");
}
}catch (err) {
response
.status(500)
.json({ message: "Error in invocation of API: /login" })
}
});
this is my database code.
///////////////////////
import {
BrowserRouter as Router,
Route
} from "react-router-dom";
import React, { useState } from 'react'
import Navbar from '../../common/Navbar/Navbar';
import AdminPage from './AdminPage';
const User = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState([]);
const [error, setError] = useState('');
const onSubmitForm = async e => {
e.preventDefault();
try {
const response = await fetch(`http://localhost:2136/login`,{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
console.log(email,password)
console.log(response);
if ((response.status)===404) {
setError('Invalid email or password');
} else
{
window.location.replace(`/AdminPage`);
}
} catch (err) {
console.error(error);
setError('An error occurred. Please try again later.');
}
};
return (
<>
<Navbar/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"></link>
<div className="container text-center">
<h1 className="my-5">Search for the Dormitory You Want</h1>
<form className="d-flex" onSubmit={onSubmitForm}>
<input
type="text"
name="name"
placeholder="Enter email ..."
className="form-control"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<input
type="text"
name="name"
placeholder="Enter password ..."
className="form-control"
value={password}
onChange={e => setPassword(e.target.value)}
/>
<button className="btn btn-success">Submit
</button>
</form>
</div>
</>
)
}
export default User
this is my login page. the page it redirects to is a blank page. i don't understand why.
///////////////
import React, { useState } from 'react'
import AdminNavbar from '../../common/Navbar/AdminNavbar';
const AdminPage = () => {
return (
<>
<AdminNavbar/>
</>
);
}
export default AdminPage
and this the page I want to redirect AdminPage.So what can i do?
///////////////////
The difference between assign() and replace():
replace() removes the current URL from the document history.
With replace() it is not possible to use "back" to navigate back to the original document.
You can use assign method instead of location method

Can't find mistake in my code, I am validating user is logined and has token stored in localstorage, but can;t get back token from localstorage

//Home page in reactJs
import React,{useState, useEffect} from 'react';
import Navbar from './Navbar';
import Footer from './Footer';
import landingpic from '../assets/images/landingpage.jpg';
let my_token;
const Home = () => {
const [userName, setUserName] = useState();
const callHomePage = async() => {
my_token = window.localStorage.getItem(my_token);
console.log(my_token);
const data = await fetch('http://localhost:5000/getdata',{
method:'POST',
headers:{
"Content-Type":"application/json",
'Authorization': "Bearer" + my_token,
},
body:JSON.stringify({my_token})
});
const res = data.json();
setUserName(res.name);
console.log(res.name);
if(!res.status===200 || !res){
const error = new Error(res.error);
throw error;
}
}
useEffect(()=>{
callHomePage();
})
return (
<div>
<Navbar/>
<div className='main_content'>
{/* left side content */}
<div className='main_content_left'>
<p>Online Learning</p>
<h1>Learn EveryDay</h1>
<p>Hey {userName} Great way to learn is by playing games.<br/>Come everyday and play these quiz challanges<br/>and learn with fun</p>
<button>Start Game</button>
</div>
{/* right side content */}
<div className='main_content_right'>
<img src={landingpic} alt="quiztime" className='quizimage' />
</div>
</div>
<Footer/>
</div>
)
}
export default Home
// getdata route
router.post('/getdata',authenticate,(req,res)=>{
res.send(req.user);
});
///authenticate middleware
const jwt = require("jsonwebtoken");
const User = require('../model/userSchema');
const authenticate = async(req,res,next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
console.log(`verified token is ${token}`);
if(token == null){
return res.sendStatus(401);
}
const verifyToken = jwt.verify(token,process.env.SECRET_KEY);
const rootUser = await User.findOne({_id:verifyToken,"tokens.token":token});
if(!rootUser) {throw new Error('User not found')};
req.token=token;
req.rootUser=rootUser;
req.userID=rootUser._id;
next();
}
module.exports = authenticate;
/// login.js frontend file
import React,{useState} from 'react'
import { useNavigate } from 'react-router-dom';
const Login = () => {
const [user,setUser] = useState({email:"",password:""});
const navigate = useNavigate();
let name;
let value;
const handleInputs = (e) =>{
name=e.target.name;
value=e.target.value;
setUser({...user,[name]:value});
}
const postdata = async(e) =>{
e.preventDefault();
const{email,password} = user;
const data = await fetch('http://localhost:5000/signin',{
method:'POST',
headers:{
"Content-Type":"application/json",
},
body:JSON.stringify({
email,password
})
});
const res = await data.json();
if(res.status===422 || !res){
window.alert("Login failed");
}
else{
window.alert("Login Successful");
window.localStorage.setItem("my_token",res.my_token);
navigate('/');
}
}
return (
<div>
<div className='outer_form_div'>
<div className='inner_form_div' style={{height: "60vh"}}>
<div className='form_heading_div'>
<h1>Log In</h1>
<p>Doesn't have an account? <a href='/signup'>Sign Up</a></p>
</div>
<div className='form_div'>
<form method='POST'>
<label>Email</label><br/>
<input type="email" name="email" value={user.email} onChange={handleInputs} autoComplete="off" placeholder="Enter your email"/><br/><br/>
<label>Password</label><br/>
<input type="password" name="password" value={user.password} onChange={handleInputs} autoComplete="off" placeholder="Enter your password"/><br/><br/>
<input type="button" value="Submit" onClick={postdata} className='submitbtn'/>
</form>
</div>
</div>
</div>
</div>
)
}
export default Login
In my this code I am trying to validate that if user is authorized then display its name on home page, the user if has jwt token stored in localStorage it send token through auth. headers to server on route /getdata. The problem is that the token when generated gets stored in localStorage but after when user is redirected to home page the token is sadi to be null. I can't figure out the mistake, please anyone help me with my mistake. It would be a great help.
You are trying to do a .getItem from local storage using the variable you want to store the value of the result as the key.
Try doing something like this instead.
my_token = window.localStorage.getItem("<your storage key>");
https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem

I am getting "POST http://localhost:5000/api/users 400 (Bad Request)" response while using axios.post from action in react

I am using axios.post in my auth action by which I want to post my register detail that I am getting from register component . But I am getting bad request response and with error
"Error: Request failed with status code 400
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.onloadend (xhr.js:66)"
Please find the below code for my Auth action ,Register component and server code for registering users please let me know the mistake that I making here :
//auth action :
import axios from "axios";
import {setAlert} from './alerts';
import {REGISTER_FAIL,REGISTER_SUCCESS} from './types';
// Register User
export const register = ({name,email,password}) => async (dispatch) => {
const headers = {
'Content-Type': 'application/json'
}
const body=JSON.stringify({name,email,password});
console.log(body);
try {
const res = await axios.post('http://localhost:5000/api/users', body, {
headers: headers
})
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
/*
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
*/
console.log(err);
dispatch({
type: REGISTER_FAIL
});
}
};
// , "proxy": "http://localhost:5000"
// Register component
import React from 'react';
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { setAlert } from '../../actions/alerts';
import { register} from '../../actions/auth';//importing register action
import './../../App.css';
export const Register = (props) => {
const [formData, setFormData] = useState({
name:'',
email:'',
password:'',
password2:''
})
const {name,email,password,password2}={...formData};
const onChange=(e)=>setFormData({...formData,[e.target.name]:e.target.value});
const onSubmit=(e)=>{
e.preventDefault();
if(password!==password2){
props.setAlert('password do not match','danger');
}
else{
props.register({name,email,password});
}
}
return (
<div >
<h1 className="large text-primary">Sign Up</h1>
<p className="lead"><i className="fas fa-user"></i> Create Your Account</p>
<form className="form" onSubmit={e=>onSubmit(e)}>
<div className="form-group">
<input type="text" placeholder="Name"
name="name" value={name}
onChange={e=>onChange(e)}
required />
</div>
<div className="form-group">
<input type="email"
placeholder="Email Address"
name="email" value={email}
onChange={e=>onChange(e)} />
<small className="form-text"
>This site uses Gravatar so if you want a profile image, use a
Gravatar email</small
>
</div>
<div className="form-group">
<input
type="password"
placeholder="Password"
name="password"
value={password}
onChange={e=>onChange(e)}
minLength="6"
/>
</div>
<div className="form-group">
<input
type="password"
placeholder="Confirm Password"
name="password2"
value={password2}
onChange={e=>onChange(e)}
minLength="6"
/>
</div>
<input type="submit" className="btn btn-primary" value="Register" />
</form>
<p className="my-1">
Already have an account? <Link to="/login">Sign In</Link>
</p>
</div>
)
}
export default connect(null,{setAlert,register}) (Register);
Server API code :
const express =require('express');
const User=require('../../models/User');
const gravatar=require('gravatar');
const {check, validationResult}=require('express-validator');
const bcrypt=require('bcryptjs');
const jwt=require('jsonwebtoken');
const router=express.Router();
const config=require('config');
// signin
router.post('/',[
check('name','name is required').not().isEmpty(),
check('email','Need valid email').isEmail(),
check('password','length should be greater than 6').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({errors:'user already exist !'})
}
const avatar =gravatar.url({email})
user= new User({
name,
email,
password,
avatar
})
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'),(err,token)=>{
if(err){
throw err;
}
res.json({token});
})
}
catch(err){
return res.status(400).json({errors:errors.message})
}
});
module.exports=router;

Prooblem connecting the front with the back

I have a problem, I don't know what I touched but it seems that my front does not connect to the back.
-With the postman, the back gives me the token, so I don't think it's a problem with the back.
-But when I try to log in I get "Cannot POST / Admin", admin is simply the route not by admin permissions.
-In the console of the page I see "Failed to load resource: the server responded with a status of 404 (Not Found)"
So it seems that there is no connection between the two.
-While in the back console it puts the same thing all the time, a console that I put and that there are no errors "OPTIONS / api / auth 204 0.089 ms - 0
POST / api / auth - - ms - -
Connected successfully to server "
-Another error that has come out to me sometimes, although I think it is a warning is this "(node: 10388) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option {useNewUrlParser: true} to MongoClient.connect. "
In short, I don't know what I've touched and I've been looking at it for two days and I don't know where to go.
Right now I am using React, Nodejs, Redux and MongoDB
Login.tsx
import React, { useState } from "react";
import "../styles/Admin.css";
import { connect } from "react-redux";
import * as actions from "../actions/actions";
interface IProps {}
interface IPropsGlobal {
setToken: (t: string) => void;
setName: (u: string) => void;
}
const Login: React.FC<IProps & IPropsGlobal> = props => {
const [username, setUsername] = React.useState("");
const [password, setPassword] = React.useState("");
const [error, setError] = React.useState("");
const updateUsername = (event: React.ChangeEvent<HTMLInputElement>) => {
setUsername(event.target.value);
setError("");
};
const updatePassword = (event: React.ChangeEvent<HTMLInputElement>) => {
setPassword(event.target.value);
setError("");
};
const signIn = () => {
fetch("http://localhost:3006/api/auth", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: username,
password: password
})
})
.then(response => {
console.log(response);
if (response.ok) {
response
.text()
.then(token => {
console.log(token);
props.setToken(token);
props.setName(username);
});
} else {
setError("Usuario o Contraseña incorrectos");
}
})
.catch(err => {
setError("Usuario o Contraseña incorrectos.");
});
};
return (
<div>
<div className="section"></div>
<h5 className="indigo-text">Please, login into your account</h5>
<div className="section"></div>
<div className="container">
<div className="z-depth-1 grey lighten-4 row er" >
<form className="col s12" method="post">
<div className='row'>
<div className='col s12'>
</div>
</div>
<div className='row'>
<div className='input-field col s12'>
<input className='validate' name='email' id='email' value={username}
onChange={updateUsername}/>
<label >Enter your email</label>
</div>
</div>
<div className='row'>
<div className='input-field col s12'>
<input className='validate' type='password' name='password' id='password' value={password}
onChange={updatePassword} />
<label >Enter your password</label>
</div>
<label >
<a className='pink-text' href='#!'><b>Forgot Password?</b></a>
</label>
</div>
<br />
<div className='row'>
<button type='submit' name='btn_login' className='col s12 btn btn-large waves-effect indigo'
onClick={signIn}>Login</button>
</div>
</form>
</div>
</div>
Create account
</div>
);
};
const mapDispatchToProps = {
setToken: actions.setToken,
setName: actions.setName
};
export default connect(
null,
mapDispatchToProps
)(Login);
and here Api.js
const express = require('express');
const jwt = require('jsonwebtoken');
const mongo = require('mongodb');
const assert = require('assert');
const router = express.Router();
const md5 = require('md5');
// Connection URL
const mongoUrl = 'mongodb://localhost:27017';
// Database Name
const mongoDBName = 'ardalesturweb';
/* GET users listing. */
router.get('/', (req, res) => {
res.send('respond with a resource');
});
const secret = 'mysecret';
router.post('/auth', (req, res) => {
mongo.MongoClient.connect(mongoUrl, (err, client) => {
assert.equal(null, err);
console.log('Connected successfully to server');
const db = client.db(mongoDBName);
console.log(req.body.username)
const query = db.collection('admin').find({
username: req.body.username,
password: md5(req.body.password),
});
query.toArray().then((documents) => {
if (documents.length > 0) {
const token = jwt.sign(
{
_id: documents[0]._id,
username: documents[0].username
},
secret,
// {
// expiresIn: 86400
// }
);
res.send(token);
} else {
res.status(400).send('Invalid credentials');
}
});
client.close();
});
});
Cannot POST /
when the postman works with api. I don't know if I have put something wrong in the front, because the back seems to be correct
I don't have enough points to comment, so here are a few things you should clarify in the question and might help you in fixing the problem.
404("Not Found") is the error that you report, it means the server does not have a handler for the relevant endpoint so make sure the /Admin endpoint is correct. It is not present in the Api.js file. Make sure the middleware prefix is correct; is it /api/admin or just /admin .
the /api/auth endpoint seems to be working correctly; which is why the console is working. You have probably not posted the code for the frontend where the call to /admin is being made.
Comment/Edit with these suggestions and maybe we can solve this.

Resources