I am using the express framework in node and I don't know what is best practice or if this is the wrong thing to do but I wanted to send a status code e.g. res.status(200).send("Success"); if the form input matches with the server and if it does not match then send something like res.status(403).send("Forbidden");
Then in the webpage I can update the paragraph element with the sent response. So the user knows if it has been successful or not.
Is this possible? If it is how do I do it? And is there a better way?
For sure it is possible!
Taken from the express api reference:
res.status(code)
Sets the HTTP status for the response. It is a chainable alias of Node’s response.statusCode.
res.status(403).end()
res.status(400).send('Bad Request')
res.status(404).sendFile('/absolute/path/to/404.png')
Generally sending status codes is the way to go. If you are sending data without a status code, express will add the 200 status code automatically, so you don't have to add it manually.
On the client side, you have to check for a non 2xx status code in your response object of your request. Here is an example using the fetch api.
fetch('/your/api')
.then((response) => {
if (!response.ok) { // Check for a non 2xx status code
throw new Error('Network response was not ok');
}
// Do something with the response data
})
.catch((error) => {
// This is only reached when a network error is encountered or CORS is misconfigured on the server-side
console.error('There has been a problem with your fetch operation:', error);
});
Example: Credentials Use Case
If you want to write a web page which has a form to enter user credentials to gain access to further content, I would suggest doing it the following way:
Client side:
// Function is listening to the submit event of your login form
function submitLoginForm() {
let username = document.getElementById("username").value;
let password = document.getElementById("password").value;
const options = {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: {
'Content-Type': 'application/json'
}
};
return fetch('/api/login', options)
.then((response) => {
// Check for a non 2xx status code
if (!response.ok) {
// Show a failed login hint
showMessageBox('Login was not granted by the server. Please check you user name or password and try again.', 'error');
}
// Your login was successfull, manually redirect to user's dashboard, or whatever content...
})
.catch((error) => {
// This is only reached when a network error is encountered or CORS is misconfigured on the server-side
console.error('There has been a problem with your fetch operation:', error);
});
}
Server side:
app.post('/api/login', (req, res, next) => {
let username = req.body.username;
let password = req.body.password;
checkCredentials(username, password, (err) => {
if (err) {
return res.status(400).send('Wrong user name or password.');
}
// Consider adding a token or a cookie to the response object, so that the user keeps logged in.
return res.send('Access granted.');
});
});
Related
EDIT - I no longer think it is a development environment issue as the same thing persists in production.
First off, appreciate anytime spent on this - I know that this is a commonly asked question, but I've perused so many questions and tried applying so many of the solutions to no avail.
To summarize, I currently have a React Front-end that is sending request with the Fetch API to my Express server. I want to use express-sessions to save my logged in user information for the session to use and authenticate as the user uses the application.
I'm having this weird issue where every time a new request is sent, the cookie in my browser in the application tab gets set to something completely new. I might be understanding this wrong, but I thought that that was supposed to remain consistent throughout the session, but it doesn't seem to be the case. This is making it so that every time I call a new request, req.session is completely reset with nothing.
Here is some code from my application:
Express- server.js [my setup for express-session and cors]
app.use(cors({
credentials: true
}))
const session = require('express-session')
// Middleware for creating sessions and session cookies.
// A session is created on every request
app.use(session({
secret: 'tis a secret mate',
cookie: {
expires: 3000, // expires in 15 mins
httpOnly: true,
secure: false,
},
// Session saving options
saveUnintialized: false, // don't save the initial session if the session object is unmodified (i.e the user did not log in)
resave: true, // don't resave a session that hasn't been modified
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI_SESSIONS
})
}))
app.use(function (req, res, next) {
console.log('SESSION LOGGING MIDDLEWARE')
console.log(req.session);
next()
});
app.use((req, res, next) => {
res.header('Access-control-Allow-Origin', 'http://localhost:3000');
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
);
res.header('Access-Control-Allow-Credentials', true);
next();
});
EDIT - Login Route
app.post('/api/users/login', async (req, res) => {
// pull out the body
const body = req.body
// pull out the username and password
const username = body.username
const password = body.password
User.findByUsernamePassword(username, password)
.then(user => {
// if we find the user, the users information to the session to retain all that information
// will create a function to ensuree that the session exists to make sure that we are logged in
req.session.user = user
req.session.username = user.username
req.session.save()
console.log(req.session)
res.send({ user: req.session.user })
})
.catch(error => {
res.status(400).send()
});
})
Front-end Fetch Calls
In the code below, I pasted my login and check-session calls to their respective endpoints. Essentially the check session call just returns what is in the session, and the login call is supposed to login the user and add the user to req.session.user
After logging in, I try to run check-session to see if the user is in the session, but it is not there every time. I also notice that the cookie in my applications tab for chrome changes to something else.
export const login = (username, password, setUser) => {
const obj = {
username: username,
password: password
}
const request = new Request(`${API_HOST}/api/users/login`, {
method: "POST",
credentials: "include",
body: JSON.stringify(obj),
headers: {
"Content-Type": "application/json"
}
})
// send the request
const promise = fetch(request)
.then(res => {
if (res.status === 200) {
return res.json();
}
})
.then(json => {
// if the user exists, setUser to user
console.log("USER PRE:", json.user)
if (json.user !== undefined) {
console.log("USER: ", json.user)
setUser(json.user)
return json.user
}
})
.catch(error => {
console.log(error);
})
return promise
}
export const checkSession = () => {
const url = `${API_HOST}/api/users/check-session`
fetch(url, {
credentials: 'include'
})
.then(res => {
if (res.status === 200) {
console.log('200')
console.log(res.json())
} else {
console.log(res)
}
})
// .then(json => {
// if (json) {
// setUser(json)
// }
// })
.catch(error => {
console.log(error);
});
}
Things I've Tried
I've tried changing things to how my cookie is defined in server.js (secure: false | httpOnly: true)
I've tried adding credentials to my fetch calls and accepting them in cors in server.js
I changed my chrome to disable-web-security
I added cors access control stuff to the response header as seen above
Additional notes that might be helpful
I'm using React with React Router, not sure if that would affect stuff
Thank you for your time, I know this is a super long question. Please let me know if there is anything else I could provide to help clarify if you want to help!
If you're running the backend and frontend on different hosts, there could be a CORS problem. Also you said that you checked the cookie in the application tab, but if you haven't tried looking at the request and response cookie headers in the network tab, you should check those out to further diagnose the problem. The cookie should be set on the authentication request, and it should be included in the requests following that.
I've handled my server side's basic auth with nodejs, Please refer the code below.
module.exports = basicAuth;
// require("dotenv").config({ path: "./.env" });
require("dotenv/config");
// async function basicAuth(req, res, next) {
function basicAuth(req, res, next) {
// check for basic auth header
if (
!req.headers.authorization ||
req.headers.authorization.indexOf("Basic ") === -1
) {
return res.status(401).json({ message: "Missing Authorization Header" });
}
console.log(req.headers.authorization);
// verify auth credentials
const base64Credentials = req.headers.authorization.split(" ")[1];
// console.log(base64Credentials);
const credentials = Buffer.from(base64Credentials, "base64").toString(
"ascii"
);
const [username, password] = credentials.split(":");
// const user = await userService.authenticate({ username, password });
let user = 0;
if (
username == process.env.API_USERNAME &&
password == process.env.API_PASSWORD
) {
user = 1;
}
if (!user) {
return res
.status(401)
.json({ message: "Invalid Authentication Credentials" });
}
next();
}
I've added app.use(cors()) in my app.js and I'm able to access all routes using basic authentication.
I've written my front end application using react and I'm using axios to fetch the data using the routes that I created. Please note the same API's work when I try to access it without using basic auth.
Below is the code for accessing data using axios.
try {
require("dotenv").config();
console.log(this.state.params);
let urlparam = "http://localhost:5000/users/" + this.state.params;
let result;
result = await axios({
url: "http://localhost:5000/users",
method: "get",
withCredentials: true,
headers: {
authorization: "Basic c2Fsb29uOnNhbG9vbg==",
},
});
} catch (err) {
console.log(err);
}
Using the above code I get:
The requested resource requires user authentication.
on Edge browser and on Google chrome I get the error:
Access to XMLHttpRequest at 'http://localhost:5000/users' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
and
xhr.js:178 GET http://localhost:5000/users net::ERR_FAILED
Please bear in mind I've added and used the cors middleware for all routes and it was working previously.
I even tried passing auth parameters separately like
auth:{username:"",password:""}
it still wont work
I had to include
app.use(basicAuth) below app.use(cors()) in the app.js file.
I've built a react native app that has a Node js backend. Users can sign In, sign up and view a profile page.
All my users can sign In but some of them can't view the profile page.
When I look at the request made to my backend, I get:
POST /UserRouter/SignIn 200 212.537 ms - 130342
Signing in works, it finds the user, returns the JWT token. When it's in the app no other requests are made. I get JSON Parse error: Unexpected EOF
Once you sign in, its supposed to immediately make a request to get your profile. With some accounts, this doesn't happen
My initial hypothesis of this problem is that the token for some users has expired, so they are not able to access protected routes. I use p***assport-jwt*** for my tokens. Hence, the backend not registering any requests.
Please find my code below:
_fetchData = () => {
AsyncStorage.getItem('jwt', (err, token) => {
fetch(`${backendUri }/UserRouter/Profile`, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: token
}
})
.then((response) => response.json())
.then((json) => {
this.setState({name:json.name})
})
.catch((error) => {
console.log(error)
alert('There was an error ')
})
.done()
})
}
Here is my node JS code
app.get('/UserRouter/profile', passport.authenticate('jwt1', { session: false }), function (req, res) {
const token = req.headers.authorization
const decoded = jwt.decode(token.substring(4), config.secret)
User.findOne({
_id: decoded._id
},
function (err, user) {
if (err) throw err
res.json({ email: user.email, name: user.fName})
})
})
Thank you
This was the answer: https://stackoverflow.com/a/33617414/6542299
Turns out I was encoding my token with the users' document. the users' document was too large. so I just needed to reduce it
I wrote a rest api to handle all requests for my chat app, such as logging in, signing up, or sending a message. This works fine for my serverside code;however, I want logging in or signing up to send a request to the rest api instead. On the api, the request is never received from the client. I tried testing it basically in a similar way on a separate directly
I'm working on a chat app I started around a month ago
Code for server that handles logins:
app.route('/login').post(async (req, res) => {
console.log('login request')
var users = await serverController.listDocuments({
database:'chatbase',
collectionName:'users',
query:`{'username':'${req.body.usrnm}'}`
})
if(!users[0]) return res.send('Incorrect Credentials')
var user = new User(users[0])
var isCorrect = await bcrypt.compare(req.body.psw, user.password)
if(!isCorrect) return res.send('Incorrect Credentials')
res.send({token: user.token, username:user.username, id:user.id})
})
Code for client side:
let xhr = new XMLHttpRequest()
xhr.open("POST", 'https://api.domain.com')
xhr.setRequestHeader('Content-Type', "application/json");
xhr.onreadystatechange = () => {
if (xhr.status == 200) {
alert('Reqest succeeded');
}
alert(xhr.status)
}
xhr.send(JSON.stringify({
usrnm:'Example Username',
psw:'Example Password'
}))
Code for server(Works):
socket.on('login', credentials => {
var opt = {
uri:'https://message-api.glitch.me/login',
method:'POST',
body:credentials,
headers: {
'User-Agent': 'Request-Promise'
},
json:true
}
rp(opt)
.then(res => {
if(res == 'Incorrect Credentials') return socket.emit('loginFailed')
socket.emit('loginSuccess', res)
})
})
The serverside code receives creds from user and then forwards them to api. The server then receives either a token that the user can send messages using or it sends an incorrect login(Works). The api code simply routes various request types, such as creating servers or logging in(Works from serverside requests). The client side creates a reqyest and then sends it(Is never recieved)
I'm trying to do a redirect after an ajax put request. I plan on using pure JS client side for validation.
Client:
$(document).ready(function() {
login = () => {
var username = $("[name='username']").val()
var password = $("[name='password']").val()
$.ajax({
type: "put",
url: '/login',
data: {
username: username,
password: password
}
// success: function(response) {
// console.log('Success:')
// console.log(response.user)
// Cookies.set('username', response.user.username)
// Cookies.set('first_name', response.user.first_name)
// Cookies.set('last_name', response.user.last_name)
// Cookies.set('email', response.user.email)
// window.location.href = window.location.origin + '/'
// },
// error: function(error) {
// console.log("Error:")
// console.log(error)
// }
})
}
logout = () => {
console.log("Log out clicked.")
Cookies.remove('username')
Cookies.remove('first_name')
Cookies.remove('last_name')
Cookies.remove('email')
window.location.href = window.location.origin + '/logout'
}
})
Server:
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('main')
});
router.put('/login', function(req, res) {
// Password is not encrypted here
console.log('req.body')
console.log(req.body)
User.findOne({ username: req.body.username }, function(err, user) {
// Password is encrypted here
if (err) throw err
console.log('user')
console.log(user)
bcrypt.compare(req.body.password, user.password, function(err, result) {
if (result) {
var token = jwt.encode(user, JWT_SECRET)
// return res.status(200).send({ user: user, token: token })
return res.redirect('/')
} else {
return res.status(401).send({error: "Something is wrong."})
}
})
})
})
I can't get main.hbs to render after a successful login. My commented code works, but I'm trying to do my redirect server side rather than client side because I'm told that it's better for security.
You should know when to use href and replace functionalities.
window.location.replace(...) will best way to represent an HTTP redirect.
Reason
When compared to window.location.href, window.location.replace(...) is better to use in a HTTP redirect scenario because replace() avoids keeping the originating page in the session history, this helps users to avoid get stuck in a never-ending back-button fiasco.
Summary
If you want to illustrate clicking on a link, use location.href
If you want to illustrate an HTTP redirect, use location.replace
Sample
// an HTTP redirect
window.location.replace("http://example.com");
// clicking on a link
window.location.href = "http://example.com";
Update
The server cannot do a redirect from an ajax request. In the end ajax involves the client (browser).
If you want, you can send the instruction of a redirection through the server side call, but it will be end up again on client side, in the callback.
You can do that by returning an object from the server which contains the url you want to redirect to. Then use javascript to change the document's location property. Like Below:
Server Side Code
if (result) {
var token = jwt.encode(user, JWT_SECRET)
return res.status(200).send({result: 'redirect', url:'/'})
} else {
return res.status(401).send({error: "Something is wrong."})
}
And then in Client Side Javascript:
$.ajax({
type: "put",
url: '/login',
data: {
username: username,
password: password
}
success: function(response) {
if (response.result == 'redirect') {
//redirecting to main page from here.
window.location.replace(response.url);
}
}
});
Apart from this your commented code is the correct way to do this. Just like one of the comments in you question "server side redirect is deadend for an ajax request as the instruction is not for the browser but some javascript handler."
I don't think what you want to do is possible. An AJAX request is meant just to pass data back and forth. What happens now is that you need to script client side behavior on your end. This means the AJAX request will pass a 302 and other data that comes along for the ride to the callback on JS. No client side behavior can be altered from the server. It is up to you to do something with the AJAX returned values. If 500, throw an error message, 200 do something etc.
The only way you can get a server redirect to work is by traditional HTML form submission.