I am developing a web app with an Express sever, and I can't find the reason for the page not to load after I call res.render().
My folder structure is as follows (omitting the files and folders that are unnecessary for this question):
- app
- views
- partials
adminDashboard.ejs
welcome.ejs
- lib
- controllers
auth.controller.js
- models
user.model.js
- routes
routes.js
- public
- js
login.js
server.js
The code in question is as follows:
login.js
loginForm.addEventListener('submit', (event) => {
event.preventDefault()
const username = document.getElementById("inputUsername").value
const password = document.getElementById("inputPassword").value
const user = {
username: username,
password: password
}
return fetch(`/login`, {
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(user)
})
.then(response => window.location.reload)
})
auth.controller.js
async function login(request, response) {
try {
//We must validate what is sent by the frond end
if (!(request.body.username, request.body.password)) {
response.status(400).send("Inputs missing")
}
//Next we verify if the user actually exists in the database
const existingUser = await User.getByUsername(request.body.username);
//We will include the password comparison logic in this if block
if (existingUser.status == 201 && (await bcrypt.compare(request.body.password, existingUser.user.Password))) {
//Create a more lean user object
const loggedUser = existingUser.user
delete loggedUser.Password
const token = jwt.sign({
username: request.body.username
},
process.env.TOKEN_KEY
)
loggedUser.token = token
return response.status(200).render('adminDashboard.ejs')
} else {
response.status(400).send("User not found")
}
} catch (err) {
//No better way of handling this error as of now
console.log(err)
}
}
async function isLoggedIn(req, res, next) {
const authHeader = await req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
if (token == null) return res.status(200).render('welcome.ejs')
jwt.verify(token, process.env.TOKEN_KEY, (err, user) => {
if (err) return res.status(403).redirect('/login')
//req.user = user
next()
})
}
module.exports = {
login: login,
isLoggedIn: isLoggedIn
}
routes.js
var router = require("express").Router()
const auth = require("../controllers/auth.controller.js")
router.post("/login", auth.login)
Now, the problem is whenever I click login in the login page and it runs the fetch with the user credentials, nothing happens. I've used console.log to see up to where everything runs and found that everything runs as it should but when response.render is called in auth.controller.js nothing happens and the browser stays in the login page.
Thank you very much in advance!
Related
Here is the situation:
I have a database which contains a user and password registered.
My assignment, for now, is to create a login form, and login with a registered uname and pw.
Uname and pw are registered in the server/database already.
ps: I did not create the server nor database.
Node server code
import express from 'express';
import cors from 'cors';
import http from 'http';
import { Sequelize } from 'sequelize';
import { Data } from './database';
import { router } from './routes/Router';
import { initialData } from './database/someData';
const closeServer = async (
server: http.Server,
sequelize: Sequelize,
signal?: string
) => {
server.close();
await sequelize.close();
process.exit();
};
const runServer = async (): Promise<void> => {
const PORT = process.env.PORT || 8082;
const app = express();
const sequelize = Data.init();
app.use(
cors({
credentials: true,
origin: 'http://localhost:3000',
})
);
app.use('/api', router);
const server = app.listen(PORT, () => {
console.log(`Starting server at ${PORT}`);
});
try {
await sequelize.authenticate();
await sequelize.sync({
force: process.env.SERVER === 'reset',
});
if (process.env.SERVER === 'reset') await initialData();
} catch (e) {
closeServer(server, sequelize);
throw e;
}
};
runServer()
.then(() => {
console.log('Run successfully');
})
.catch((ex: Error) => {
console.log('Unable to run:', ex);
});
I need help on what is that I have to do.
When I input username and pw, on the form, what are the methods to use for sending the info?
And then, when the info reaches the server, i think the username and pw need to be validated with jwt, and then check if the user and pw exists. how do i do that?
What i have understood so far is that i gotta use axios to send info to server, but thats it.
Do i need to use jwt for the login?
What is the normal flow for this kind of mechanism?
I am using react as a framework.
So there are quite few steps here.
First you have to create endpoint on your backend server for issuing jwt tokens. Jwt tokens can be used as a pass for user to login. So in your router you would add something like this:
router.post('/login', (req, res)=> {
const username = req.body.username
const password = req.body.password
// Then you make db call to verify username and password are correct.
if Credentials are valid, you would issue jwt token
jwt.sign({
// here you can save extra information of user. Also remember this information must be public since anyone can see it. Do not put user password here
email: 'email',
userId: 'id',
}, "secret")
})
After this, you need some kind of middleware on backend, so that on each user request, you check and verify this jwt token which is sent from react application. For example you could write isAuth middleware:
const jwt =require("jsonwebtoken");
export const isAuth= (req, res, next) => {
try {
// here we attach request in auth header, with Bearer "jwt token" format. So we extract jwt token and verify it
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ message: "no token" });
}
const token = authHeader.split(" ")[1];
let decodedToken;
decodedToken = jwt.verify(token, "secret");
if (!decodedToken) {
return res.status(401).json({ message: "Wrong token" });
}
req.userId = decodedToken.userId;
next();
} catch (err) {
console.error(err);
return res.status(401).json({ message: err });
}
};
Now you would be able to have backend endpoints like this:
// This is how you would require login on some routes
router.post("/getMyPrivateInfo", isAuth, QueryPrivatInfo)
Now on React side, you would make request for login like this:
axios.post("/login", {
username: '1',
password: "2"
})
This would return jwt token, now you would save this token in local storage.
After its saved in local storage and you make request with axios for private info you would do following
axios.post("/getMyPrivateInfo", {any request body info neeeded}, {
headers: {
Authorization: "Bearer jwtTokenFromLocalStorage"
}
})
This is how whole flow will work, hope it makes sense
I have created a login page and a about page the user will only access the about page if the user is logged in.
I am trying to authenticate the user by using the tokens generated while signing in, but the token is not getting authenticated even after signing in with the correct credentials. I don't know what is the problem?
This is code to my sign-in and token generating method
const express = require("express");
const { default: mongoose } = require("mongoose");
const router = express.Router();
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
require("../db/conn");
const User = require("../model/userSchema");
const cookieParser = require('cookie-parser');
const Authenticate = require("../middleware/authenticate");
router.use(cookieParser());
//LOgin route
router.post("/signin", (req, res)=>{
if(!req.body.email || !req.body.password){
return res.status(400).json({error: "Plz fill the required data"});
}else{
bcrypt.hash(req.body.password, 12, function (err, hash) {
User.findOne({email: req.body.email}, function (err, foundUser) {
if(err){
console.log(err);
}else{
if(foundUser){
bcrypt.compare(req.body.password, foundUser.password, function (err, result) {
if(result){
return res.json({message: "successfully log in"})
}else{
return res.json({message: "incorrect password"});
}
});
const email = req.body.email;
const token = jwt.sign(
{ user_id: foundUser._id, email },
process.env.TOKEN_KEY,
{
expiresIn: "720h",
}
);
foundUser.tokens = foundUser.tokens.concat({token: token});
foundUser.save();
// res.status(200).json(foundUser);
console.log(foundUser);
}else{
return res.status(400).json({message: "user not found"});
};
}
})
})
}
});
//about us page
router.get("/about", Authenticate, function (req, res) {
console.log("about running");
res.send(req.rootUser);
});
module.exports = router;
this is the code to authenticate the user
require("dotenv").config({path: "./config.env"});
const jwt = require("jsonwebtoken");
const User = require("../model/userSchema");
const Authenticate = async(req, res, next) =>{
try {
const token = req.cookies.jwtoken;
const verifyToken = jwt.verify(token, process.env.TOKEN_KEY);
const rootUser = await User.findOne({ _id: verifyToken._id, "tokens.token": token});
if(!rootUser) {
throw new Error("User not found")
}
req.token = token;
req.rootUser = rootUser;
req.userID = rootUser._id;
next();
} catch (err) {
console.log(err);
return res.status(401).send("Unauthorized: No token provided");
}
}
module.exports = Authenticate;
This is react based code of: About-page to display it or not based on user's authenticity.
const navigate = useNavigate();
const callAboutPage = async() =>{
try {
const res = await fetch("/about",{
method: "GET",
headers: {
Accept: "application/json",
"Content-Type" : "application/json"
},
credentials: "include"
});
const data = await res.json();
console.log(data);
if(!res.status === 200){
const error = new Error(res.error);
throw error;
}
} catch (err) {
console.log(err);
navigate("/login");
}
}
As said in the comment looks like there is a issue on the process for setting up the jwtoken, and when you sign in, you just need to find the user and compare the password, there is no need to do the hash with Bcrypt, since you're not registing new user, for example, i will use Async/await instead of callback function, in order for you to read it much more easier:
//Login route
router.post("/signin", async (req, res)=> {
const { reqEmail, reqPassword } = req.body; //destructuring so less thing to write at the next step
if(!reqEmail || !reqPassword) {
return res.status(400).json({message: "Plz fill the required data"});
}
try {
const foundUser = await User.findOne({email: reqEmail})
if(!foundUser) {
return res.status(400).json({message: "Wrong username or password!"})
}
const result = await bcrypt.compare(reqPassword, foundUser.password);
if(!result){
return res.json({message: "Wrong username or password!"})
} else {
const accessToken = jwt.sign(
{ user_id: foundUser._id, email: foundUser.email},
process.env.TOKEN_KEY,
{ expiresIn: "720h",}
);
// I am confuse what are you trying to do here, in your place I would set up on the cookie since you do that on your authentification.
res.cookie("jwt", accessToken, {
maxAge: 60000, // 60 sec for testing
httpOnly: true,
sameSite: false, //false only for dev
secure: false, //false only for dev
})
res.status(200).json(foundUser);
};
} catch (error) {
return res.status(500).json({message: `${error}`})
}
Than the authentification middleware :
// ...
const Authenticate = (req, res, next) => {
const accessToken = req.cookies.jwt
if(!accessToken) {
return res.status(401).json({error: "Unauthorized: No token provided"});
}
try {
const user = jwt.verify(accessToken, process.env.TOKEN_KEY)
if(user) {
req.user = user
return next();
}
} catch (error) {
return res.status(403).json({error: "Forbidden token error"})
}
}
about page component it's simple for now since you don't manage any state
const navigate = useNavigate();
const callAboutPage = async() =>{
try {
const res = await fetch("/about",{
headers: {
"Content-Type": "application/json"
},
credentials: "include"
});
if(res.status === 200){
const data = await res.json();
// set up the state for rendering
console.log(data);
} else {
// you can also create a state to catch the error message from the backend, in this case the response json should be move to above the if statement.
throw new Error("You must log in to get access")
// than you can display this error message, or from the backend using state for this bloc, and the catch bloc
// navigate to /login
}
} catch (err) {
console.log(err);
navigate("/login");
}
}
router.use(cookieParser());
Try to use cookieParser with app.use instead. (app from express instense)
Expample:
const app = express();
app.use(cookieParser());
and try to put it before server listening in index.js or app.js file.
Hope it help.
I want to redirect to dashboard if user is already logged in when he try to access login route in express js
Middleware
const isAuthenticate = async (req, res, next) => {
const token = req.cookies.jwt;
if (token) {
jwt.verify(token, "thisisjwtsecret", async (err, token_decode) => {
if (!err) {
const u_id = token_decode._id;
const userData = await User.findOne({ _id: u_id });
req.user = userData;
req.isAuth = true;
next();
} else {
res.redirect("/user/login");
}
});
} else {
res.redirect("/user/login");
}
};
Route.js
// Auth Controller
const AuthController = require("../../controllers/auth/AuthController");
const { isAuthenticate } = require("../../middlewares/isAutheticated");
router.get("/user/login", isAuthenticate, AuthController.login);
router.post("/user/login", AuthController.checkLogin);
router.get("/user/register", isAuthenticate, AuthController.createUser);
router.post("/user/register", isAuthenticate, AuthController.storeUser);
module.exports = router;
LOgin function
// Showing Login Page to User
const login = (req, res) => {
return res.render("auth/login");
};
You can break out the functionality from your existing isAuthenticate() function so it just returns a result and then use that to do something like this:
const { promisify } = require('util');
const verify = promisify(jwt.verify);
// resolves if jwt cookie verifies and user found
// rejects if jwt cookie is missing or doesn't verify or user not found
async function isLoggedIn(req) {
const token = req.cookies.jwt;
if (!token) throw new Error("no jwt cookie");
const token_decode = await verify(token, "thisisjwtsecret");
let user = await User.findOne({ _id: token_decode._id });
if (!user) throw new Error("user not found");
return;
}
// Showing Login Page to User
// Or redirect to /dashboard if already logged in
const login = async (req, res) => {
try {
await isLoggedIn(req);
// already logged in, redirect to dashboard
// you MUST make sure that /dashboard does not redirect to /user/login itself
// when isLoggedIn resolves to avoid circular redirects
res.redirect("/dashboard");
} catch (e) {
// not logged in, render the login page
res.render("auth/login");
}
};
The isLoggedIn(req) function resolves if the token validates and the user is found in the database. Otherwise, it rejects. You can then use that in other routes to decide whether you want to redirect or not.
I am making a web application that allows Fortnite players to find other players to play with. Users should be able to register, login, post and comment. I have designed the frontend portion of the user login and registration features as well as the backend of the user registration but one of my requirements is that:
Before registration, the server should check whether the username provided is a real Fortnite username using the FortniteTracker API which provides user profiles using their very simple API.
Example Call: GET https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
How do I verify the username exists before allowing the user to create the account?
I have tried creating a separate endpoint for the API call from the server side but I didn't know how to implement it into my /register endpoint
script.js
function registerRequest(username,password) {
$.ajax({
url: "http://localhost:8080/register",
type: 'POST',
data: JSON.stringify({username,password}),
contentType: "application/json",
error : function(err) {
console.log('Error here!', err)
},
success: function(data) {
console.log('Success!')
// What do I put here?
}
});
}
function handleRegisterSubmit(event) {
event.preventDefault();
const username = $(event.currentTarget).find('.username-register').val()
const password = $(event.currentTarget).find('.password-register').val()
const passwordConfirm = $(event.currentTarget).find('.password-confirm').val()
if (password === passwordConfirm) {
registerRequest(username,password)
}
else {
console.error("Passwords did not match")
}
}
$(function onLoad() {
displayRegisterPage()
$(`.js-content-section`).on('submit', '.js-register-form', handleRegisterSubmit)
}
})
server.js
app.post('/register', jsonParser, (req, res) => {
const requiredFields = ['username', 'password']
for (let i = 0; i < requiredFields.length; i++) {
const field = requiredFields[i]
if (!(field in req.body)) {
const message = `Missing \`${field}\` in request body`
console.error(message)
return res.status(400).send(message)
}
}
let username = req.body.username;
let password = req.body.password;
User.findOne({username})
.then(user => {
if (user) {
const message = `username is already taken`
console.error(message)
return res.status(400).send(message)
}
else {
User.create({username, password})
.then(user => {
const userRes = {
id: user._id,
username: user.username
}
res.status(201).json(userRes)
}
)
}
})
.catch(err => {
console.error(err)
res.status(500).json({ error: 'something went horribly wrong'})
})
})
app.get('/login', (req, res) => {
const usernameReq = User.findById(req.body.username);
if (usernameReq) {
console.log(usernameReq)
res.status(201).json(usernameReq)
}
})
schema.js
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true,
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
I expect that if I register with "ninja" as a username I should be able to register since that is a valid Fortnite username. The actual output currently allows users to register with any username that isnt already taken in the database.
You would need packages like axios, request, request-promise (Promise supported version of request) etc to make the external api call. You can try implementing within the register like.
const rp = require('request-promise');
app.post('/register', jsonParser, async (req, res) => {
...
let username = req.body.username;
let password = req.body.password;
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
...
// continue rest of the code
}
Or have another middleware to call the external endpoint and do the checks like:
async function checkUser (req, res, next) {
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
if (checks ok)
// if all check ok go to next middleware
next()
else
// if checks did not succeed
// you could pass error to error handler like next(new Error("Hey you do not exist"))
// or render something here
}
Then mount it like:
app.post('/register', jsonParser, checkUser, (req, res) {
...
You can do it simply by sending the username to the API https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
It will give you a response mentioning about the user exists or not. Based on the response you can make another AJAX request to register the user only if the user does not exist.
I use a Promise request to resolve, reject when someone enters their username. It is only called onClick. in your request you will be able to determine if the call was successfull or not with the username.
I followed a tutorial to add login and registration to my Node.js app using JWT token and I'm having a hard time logging in and redirecting to my 'logged in' admin page. User registration works great, but the login portion I can't figure out.
This is the tutorial I was following:
https://medium.freecodecamp.org/learn-how-to-handle-authentication-with-node-using-passport-js-4a56ed18e81e
My code for login looks like this:
router.post('/login', auth.optional, (req, res, next) => {
console.log(req.body);
var user = {
email: req.body.email,
password: req.body.password
}
if (!user.email) {
return res.status(422).json({
errors: {
email: 'is required',
},
});
}
if (!user.password) {
return res.status(422).json({
errors: {
password: 'is required',
},
});
}
return passport.authenticate('local', { session: false }, (err, passportUser, info) => {
if (err) {
return next(err);
}
if (passportUser) {
const user = passportUser;
user.token = passportUser.generateJWT();
console.log("TOKEN: " + user.token);
res.setHeader('Authorization', 'Token ' + user.token);
return res.json({ user: user.toAuthJSON() });
}
return res.status(400).json({
errors: {
message: info,
},
});
})(req, res, next);
});
My '/admin' "logged in" route looks like this:
router.get("/admin", auth.required, function(req, res) {
res.render('admin', {
user : req.user // get the user out of session and pass to template
});
});
I'm not sure how I can redirect to my '/admin' route while also passing the token because currently I am seeing the following error after logging in. Makes sense since I am not passing the token to the '/admin' route...but how do I do that? :)
UnauthorizedError: No authorization token was found at middleware
Thanks in advance for the help!
EDIT:
Still can't figure this out and don't really understand how this flow is supposed to work...where do the headers need to be set to the token and how do I redirect to my admin page once the login is successful.
Here is my middleware code if this helps:
const getTokenFromHeaders = (req) => {
console.log("REQ: " + JSON.stringify(req.headers));
const { headers: { authorization } } = req;
if(authorization && authorization.split(' ')[0] === 'Token') {
return authorization.split(' ')[1];
}
return null;
};
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
optional: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
credentialsRequired: false,
}),
};
Your code does not have a problem. You seem to be confused with the login flow from server to client (Frontend/Web).
Let's first have a look the RESTFUL way of doing it. The article also refers to the same flow.
The RESTFUL API flow looks like this:
User requests for login:
POST: /api/v1/auth/login with username and password in request body.
If successful, user is returned with basic inforamtion and token.
If not, user is returned a 401 (Unauthorized) status code.
The login flow ends here.
The token provided earlier to the user is used to make subsequent calls to the backend, which a user can use to perform different operations on the sustem. In essence, it is the client which requests server for subsequent actions with the token provided in the login request.
So for your case, user after receiving the token should make a request for retrieving admin information from the backend.
But, I am assuming you are rendering views from your server-side and you want to render the admin view once the user is successfully logged in, and that's pretty straight forward.
Instead of your res.json() after successful login. You need to use res.render().
res.render('admin', {
user: user.toAuthJSON() // assuming your user contains the token already
})
Edit:
Since res.render() does not change the url in the browser. For that, you need to use res.redirect(). But the problem is, you can not send context in res.redirect().
To achieve that, you will need to pass in the user token as query paramter. See here.
TL;DR
// assuming you are using Node v7+
const querystring = require('querystring');
const query = querystring.stringify({
token: user.token,
});
const adminRoute = '/admin?' + query;
res.redirect(adminRoute)
And in your admin route, you need to slightly modify the code.
Verify the token belongs to a real user and get user information out of the token.
Render the admin template with user information retrieved from step 1.
router.get("/admin", function(req, res) {
// verify the token
const token = req.query.token;
const user = null;
jwt.verify(token, 'secret', function (err, decoded) {
if (err) {
res.status(401).send('Unauthorized user')
}
// decoded contains user
user = decoded.user
});
res.render('admin', {
user : user
});
});
I'm somewhat new to this as well, but I've got it working as follows.
In your server.js file:
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
app.use(passport.initialize());
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = Keys.secretOrKey;
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
// somefunction looks up the id in jwt payload and
// supplies passport the authenticated user via the "Done" function
somefunction.user(jwt_payload.id)
.then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
});
})
);
In your API definitions
const jwt = require("jsonwebtoken");
router.post("/login", (req, res) => {
const { userInfo } = req.body;
// userInfo has username and password in it
// anotherFuction validates the user id and password combo
anotherFunction(userInfo.id, userInfo.password)
.then(isAuthenticated => {
if (isAuthenticated) {
const payload = {
id: user.sAMAccountName,
firstname: user.givenName,
lastname: user.sn
};
// Sign Token with the payload
jwt.sign(
payload,
Keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: "Bearer " + token
});
}
);
} else {
// don't mind the statuses ^_^'
return res.status(401).json({ error: "Login failed." });
}
})
.catch(err => {
return res.status(400).json(err);
});
});
After calling the API you want to set the auth token. The following lets you delete the token if nothing is passed in, effectively "Logging out".
const setAuthToken = token => {
if (token) {
// Apply to every request
axios.defaults.headers.common["Authorization"] = token;
} else {
// Delete Auth Header
delete axios.defaults.headers.common["Authorization"];
}
};
If you're trying to use it in the front end, you need to use jwt_decode to pull the values from the token and set it however you deem necessary. If using redux to store login data it should look something like this. As I feel that the discussion of using localstorage for jwtToken is outside of the scope of this, just know would need to check for the token.
if (localStorage.jwtToken) {
setAuthToken(localStorage.jwtToken);
const decoded = jwt_decode(localStorage.jwtToken);
store.dispatch({
type: USER_LOGIN,
payload: decoded
});
}
Hope this helped.
From one beginner in JWT to another. Good luck.