What I want to achieve
A user, who logged in or signed up should not re-login after one hour. The restriction of one hour comes from firebase authentication, if not prevented (what I try to accomplish).
Problem
After a user is logged in via firebase authentication (signInWithEmailAndPassword) I always get null for currentUser and onAuthStateChanged.
What I tried
I'm using React (v17.0.2) using 'Create React App'. On server side I'm using NodeJS (v12). The communication between both is accomplished using axios (v0.21.1)
First I tried to send the token stored in localStorage, which came from firebase (server side), back to the server. But the server tells me, that the token is no longer valid. Server side code as follows:
module.exports = (request, response, next) => {
let idToken;
if (request.headers.authorization && request.headers.authorization.startsWith('Bearer ')) {
idToken = request.headers.authorization.split('Bearer ')[1];
console.log("idToken:", idToken);
} else {
console.error('No token found');
return response.status(403).json({ error: 'Unauthorized' });
}
admin
.auth()
.verifyIdToken(idToken)
.then((decodedToken) => {
console.log('decodedToken', decodedToken);
request.user = decodedToken;
return db.collection('users').where('userId', '==', request.user.uid).limit(1).get();
})
.catch((err) => {
console.error('Error while verifying token', err);
return response.status(403).json(err);
});
};
After that I tried the following code on client side.
handleSubmit = () => {
const userData = {
email: this.state.email,
password: this.state.password
};
axios
.post(firestoreUrl() + '/login', userData)
.then((resp) => {
console.log("token:", resp.data); //here I get a valid token
localStorage.setItem('AuthToken', `Bearer ${resp.data.token}`);
console.log("firebase.auth().currentUser:", firebase.auth().currentUser); //but this is null
})
.then((data) => {
console.log(data);
console.log("firebase.auth().currentUser:", firebase.auth().currentUser); //still null
})
.catch((error) => {
console.log("Error:", error);
});
};
What irritates me is that I get a token from firebase (server side), the token is then stored in localStorage (client side) but firebase then tells me, that the currentUser is null. But presumably they are not mutually dependent =/.
I'm able to access all secured sites in my app. I can log out and in again. But whatever I do the currentUser is null.
I also tried to run the code above in componentDidMount()-method. But no success.
I tried an approach from this link (hopefully in a way it should be), but it didn't work. Still getting null for both currentUser and onAuthStateChanged if I implement following code.
firebase.auth().onAuthStateChanged(user => {
if (user) {
console.log("state = definitely signed in")
}
else {
console.log("state = definitely signed out")
}
})
I always get logged to the console, that the user is 'definitely signed out'.
During research I noticed that the point at which I should try to get the currentUser-Status is kind of tricky. So I guess that one solution is to implement the currentUser-code at another/the right place. And here I'm struggling =/.
As I found out at a similar question here on SO, I did a bad mistake. Apparently, it's not a good idea to perform the signIn- or createUser-functionality on server side. This should be done on client side. In the question mentioned above are some good reasons for doing that on server side but in my case it's quite ok to run it on client side.
Thanks to Frank van Puffelen for leading the way (see one of the comments in the question mentioned above).
Related
I'm quite new to NodeJS so could someone explain this to me:
I've got a simple authorization on my endpoint created in express. As you can see in the code there is no JWT or session cookie set anywhere. I've also checked in browses cookies just to be sure and there is no sessionId set anywhere.
With that in mind I'd expect my app to prompt for credentials each time I refresh the page ( or at least when I manually delete all cookies, refresh cache and then refresh the page) but that's not happening. Browser is still sending request with correct authorization header (after i put credentials for the first time and refresh page) and I have no idea why.
simple endpoint code:
app.get('/cms', function (req, res) {
const reject = () => {
res.setHeader("www-authenticate", "Basic");
res.sendStatus(401);
};
const authorization = req.headers.authorization;
if (!authorization) {
return reject();
}
const [username, password] = Buffer.from(
authorization.replace("Basic ", ""),
"base64"
)
.toString()
.split(":");
if (!(username === "user" && password === "pass")) {
return reject();
}
res.status(200)
res.sendFile(path.join(__dirname+'/cms.html'));
});
I am trying to send http requests using axios to my node backend. For some reason, axios keeps returning a 500 (Internal Server Error) even when thunder client (dollar store version of postman) is able to send the request and get a proper response.
index.js (server)
app.get('/api/login', async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email })
if(user===undefined) { res.status(404).json("user not found"); }
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
})
Login.js (frontend)
const login = (email, password) => {
console.log(email + ': ' + password)
axios.get('http://localhost:8800/api/login', { email: email, password: password })
.then((response) => console.log(response))
.catch((err) => console.log(err.response))
}
err.response returns no useful data and err.response.data is a blank object. I've tried to edit the request header, but it is already 'application/json'. Again, this request works on thunder client and I made sure that the data I passed in was correct through the console.log(email + ': ' + password . I've been trying to fix this issue for hours so please help. Thank you in advance.
Update: I had previously binded the login function to an onClick to a button, but I put the axios function directly into the brackets instead of login(email, password). The issue persists.
Second Update: I followed the comments' advice and console logged the error on the terminal. It returned TypeError: Cannot read properties of null (reading 'password'). This was strange because in the function, I had console logged password and it returned the proper text. It also says that it cannot find a user within my database that uses the email I am currently using, but even when I pass in the exact email I use in thunder client requests, I still get the error. I think the data is not getting there properly.
Third Update: My hypothesis is confirmed. In index.js, I made the route console log req.body.email and it returned undefined. I passed in an object that I JSON stringified and when console logged in the browser, it returns a proper object. The request is sending an object with undefined properties although I am passing in an object with values
In this case, the issue was that the request was a get request, not a post request. Get requests do not take in data while post requests do. Here is the fix:
index.js (server)
app.post('/api/login', async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email })
if(user===undefined) { res.status(404).json("user not found"); }
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
})
If you have to receive the request parameters in body (mainly in json format) then you have to go with POST type request.
In the GET type request, you can get request parameters in the form of params and query string.
Parameters may be either part of path:
myapi/customers/123
or a query string:
myapi?customer=123
More details can be found here:
https://www.restapitutorial.com/lessons/httpmethods.html
I am working on a user login project. My front end is React, and my server side is Node/Express js. I am trying to learn about JWT to create protected routes. However, I have gotten stuck when it comes to sending my token (already successfully generated) back to my front end application. I am trying to store the token in a cookie, then send that back to my react side. It is my understanding that I must store the token on the react side before I can access a protected route on my server side, but I am at a loss as to how to proceed to accomplish this. Here is my code for the login post request on my server:
app.post('/userLogin', function(req, res) {
var {usernameLogin, passwordLogin} = req.query;
console.log(req.query);
var validateLogin = `SELECT CustomerID FROM CUSTOMER WHERE Username='${usernameLogin}' AND Password='${passwordLogin}'`;
mysqlConnection.query(validateLogin, (error, result) => {
if(error) {
console.log(error);
} else {
// Checks to see if the result object (CustomerID) exists or not.
if(Object.entries(result).length === 0) {
console.log('sorry');
} else {
console.log(result);
console.log('existing');
const token = jwt.sign({ usernameLogin: usernameLogin}, 'loginKey');
res.cookie('token', token, { httpOnly: true}).sendStatus(200);
console.log(token);
}
}
});
});
and this is my react onSubmit (called login) in my react app. Just as a side note, the "it worked" message successfully is printed to the console, but this is the point at which I do not know how to retrieve and store the token on my client side for future use to access protected routes:
login = (event) => {
event.preventDefault();
console.log(this.state);
fetch('http://localhost:3001/userLogin?usernameLogin=' + this.state.usernameLogin +
'&passwordLogin=' + this.state.passwordLogin, {method: 'POST'}
)
.then(res => {
if (res.status === 200) {
console.log("it worked!!!");
} else {
console.log('there was a problem at line 27');
}
})
};
Any insight would be greatly appreciated!!!
For the client side, you can store the token returned from server side using localStorage property.
Example to set the item: localStorage.setItem("token", <token>);
Example to get the item: let token = localStorage.getItem("token");
Docs: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Local_storage
PS: Let me know if was enough to solve your question.
I'm currently writing a program in which multiple accounts need to be authenticated through the LinkedIn REST API. This needs to be done in fairly quick succession as it's quite common to have to re-connect multiple accounts at once due to LinkedIn's fairly short-lived tokens.
The issue is that after authenticating a single account I then have to wait ~10 minutes for the session to expire before authenticating a new user. When the API is called (liLogin) it will automatically log the user in as part of the existing session.
I believe this is an intentional feature of the API as you can see mentioned in Step 5 of The Authorization Code Flow Documentation.
Other things to note. I've tried adding a random string as a query on the callback URL which was mentioned in another thread. I've also noticed the Android SDK has a logout function but can't seem to locate anything for this within the REST API.
I've pasted the part of my code that handles this below in-case this helps in anyway.
// Login
const liLogin = async (req, res) => {
const URL = `https://www.linkedin.com/oauth/v2/authorization?client_id=${config.li.id}&redirect_uri=${config.li.redirectUri}&response_type=code&state=PostSchedulerCapture321&scope=r_liteprofile%20r_emailaddress%20w_member_social`;
res.redirect(URL);
};
// Exchange temp token for access_token
const liGetUser = (req, res) => {
let token = req.query.code;
axios.get(`https://www.linkedin.com/oauth/v2/accessToken`, {
params: {
"grant_type": "authorization_code",
"code": token,
"redirect_uri": config.li.redirectUri,
"client_id": config.li.id,
"client_secret": config.li.secret
}
})
.then(result => {
return result.data.access_token;
})
.then(access_token => {
axios.get('https://api.linkedin.com/v2/me', {
'headers': {
'Authorization': `Bearer ${access_token}`
}
})
.then(result => {
res.send(result.data)
})
.catch(err => {
console.log(err);
})
})
.catch(err => {
err = {...err.config.headers, ...err.response.data};
console.log(err);
})
}
In-case anyone comes across this you can simply redirect the browser to the logout URL (https://linkedin.com/m/logout) and then use setTimeout to close the window after a few seconds.
Not an ideal solution but it works.
I am trying to build a RESTful API using Node.js w/ Express. I am fairly new to the MEAN stack, and want to use best practices. The concept I'm having trouble grasping and implementing is the following:
Restricting routes like PUT and DELETE on a user object, to only allow requests from users who 'own' this object.
One possibility I've thought of:
Creating secret token for users that matches token in DB
So when creating a user I assign them a token, store this in the DB and attach it to their session data.
Then my middleware would look something like:
router.put('/api/users/:user_id', function(req, res, next) {
// already unclear how this token should be transfered
var token = req.headers['x-access-token'] || req.session.token;
// update user (PUT /api/users/:user_id)
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.send(err);
} else if (user.token != token) {
res.json({ sucess: false, message: 'User not same as authenticated user.' });
} else {
// set new information only if present in request
if (req.body.name) user.name = req.body.name;
if (req.body.username) user.username = req.body.username;
...
// save user
user.save(function(err) {
if (err) res.send(err);
// return message
res.json({ message: 'User updated.' });
});
}
});
Questions I have regarding best practice
Is the scenario I thought of at all plausible?
What data should I use to create a unique token for a user?
Is storing the token in the session the best solution?
Sidenote
This is a learning project for me, and I am aware of libraries like Passport.js. I want to learn the fundamentals first.
I have a repo for this project if you need to see some of the surrounding code I'm using: https://github.com/messerli90/node-api-ownership
Edit
I would accept a good RESTful API book recommendation, where these points are covered, as an answer.
Edit 2
I actually found a lot of the answers I was looking for in this tutorial: http://scottksmith.com/blog/2014/05/29/beer-locker-building-a-restful-api-with-node-passport/
I was trying to do this without the use of passport.js but a lot of the concepts covered in the article made some of the mechanics of an authorized API clear to me.
If I understand your question, this is an API, and the client (not a browser) is passing the secret token (api key) in the request, in a header. Seems reasonable. Of course, you must require https to protect the api key. And, you should have a way for users to revoke/regenerate their API key.
So far, I don't think you need to store anything in the session. It seems like storing the token in the session just complicates things. Presumably, if you are going to establish a session, the client has to include the token in the first request. So, why not just require it on each request and forget the session? I think this makes life simpler for the api client.
A 'bit' too late, but if someone is still looking for an answer, here is how i did it:
router.put('/', function(req, res) {
var token = req.headers['x-access-token'];
if (!token) return res.status(401).send({auth:false, message:'No token provided'});
jwt.verify (token, process.env.SECRET, function (err, decoded) {
if(err) return res.status(500).send({auth:false, message:'failed to auth token'});
User.findByIdAndUpdate({_id: decoded.user_id}, req.body, function(err, user) {
if (err)
res.send(err);
res.json({username: user.username, email: user.email});
});
});
});
Just pass the user id that is stored in the token to the mongoose function. This way the user who sent the request can only update or delete the model with his ID.
Reading material:
Implementing Access Control in Node.JS
Found this super clear article on how to allow users to only delete replies they own. Hope it helps.
What worked for me:
.delete(requireAuth, async (req, res, next) => {
const knexInstance = req.app.get("db");
const comment = await CommentsService.getById(knexInstance, req.params.id);
if (comment === undefined) {
return res.status(404).json({
error: {
message: `Comment doesn't exist.`
},
});
}
if (comment.users_id !== req.users.id) {
return res.status(401).json({
error: {
message: `You can only delete your own comments.`
},
});
}
CommentsService.deleteComment(knexInstance, req.params.id)
.then((numRowsAffected) => {
res.status(204).end();
})
.catch(next);
})