I have the following function, which authenticates a user by creating a response header:
function authenticateUser(res, id) {
const payload = {
id
}
const token = jwt.sign(payload, config.JWT_KEY, {
expiresIn: '24h'
})
res.set('authorization', token)
res.redirect('/user/profile')
res.end()
}
However, when I redirect to the /user/profile, it outputs undefined for the authorization header:
controller.checkAuthorizaion = (req, res, next) => {
const token = getJWTToken(req)
console.log(token);
if(!token)
Router.redirect(res, '/', 401)
else {
jwt.verify(token, config.JWT_KEY, (err, data) => {
if(err)
Router.redirect(res, '/', 401)
else {
res.userId = data.id
next()
}
})
}
}
Why is it not working? How do I send my jwt token back to the client? I thought that jwt.sign() sends it automatically.
If you use jsonwebtoken then you need to call it as async function or provide a callback:
async function authenticateUser(res, id) {
const payload = {
id
}
const token = await jwt.sign(payload, config.JWT_KEY, {
expiresIn: '24h'
})
res.set('authorization', token)
res.redirect('/user/profile')
res.end()
}
getJWTToken(req) also should be called as async one:
controller.checkAuthorizaion = async (req, res, next) => {
const token = await getJWTToken(req)
console.log(token);
And, of course, inside the getJWTToken: you should call jwt.verify as an async function as well.
Authorization is a request header. It should not be used in responses and I think that most browsers ignore this header in the response.
You are sending a redirect response to the browser. The browser reads that response and sends a new request to the /user/profile endpoint. The Authorization header is not added automatically by the browser to your requests.
If you send the original request as an AJAX call, then you would have to make sure that the client you're using (e.g. axios) reads the Authorization header from the response before sending another request to the endpoint that you redirect to. I haven't checked that, but I'm pretty sure that JS clients like axios ignore the Authorization header in responses and won't automatically copy it to another request.
Apart from that, #Anatoly has some valid points about calling async functions, so have a look at that as well.
How do I send my jwt token back to the client? I thought that jwt.sign() sends it automatically.
Normally, you send the token in the body of the response and let your client read the body and store the token somewhere - in memory, local storage, etc. Another solution would be to store the token in a cookie, but that will only work if you already own the backend that you're calling with the token. (If you do own the backend, then in fact there is no point in using access tokens. You could just use plain-old HTTP sessions.)
Related
I have a login route that if a user signs in successfully, it attaches the id of the user from the database to the req.session.user property of the request object:
router.route('/za/login')
.get(onlyGuest, (req, res) => {
res.render('login', { layout: false });
})
.post(onlyGuest, async(req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password);
req.user = user;
req.session.user = req.user._id;
res.redirect('/');
} catch (err) {
console.log(err);
res.status(400).render('login', {
layout: false,
message: {
error: 'login failed!',
email: req.body.email
}
});
}
});
After this property(req.session.user) has been set, a user is directed to the home route. But there is a middleware that allows access to a certain route upon authenticated, otherwise it redirects to another page. Here is the code for the middleware:
let regex = /^\/za(\/.*)?$/; /*a route prefixed with '/za', followed by zero or /anything_else*/
app.use(async (req, res, next) => {
let url = req.originalUrl;
if(!req.session.user && !regex.test(url)) {
console.log('You are trying to access an authenticated route. Login first!');
console.log(req.session.user);
if(url === '/') {
res.redirect('/za');
return;
}
res.redirect('/za/login?forbidden');
return;
}
/*req.session.user property is true or has a value set*/
if (req.session.user) {
try {
const user = await User.findById(req.session.user);
if(!user) {
return res.redirect('/za/login?forbidden');
}
req.user = user;
return next();
} catch (err) {
console.log(err);
return res.render('errors/500');
}
}
});
After successfully logging in via the login route(providing the correct credentials), i wonder why the request.session.user is still undefined and therefore redirecting me to a page accessible by unauthenticated users.
I have a feeling that i am missing the timing of order of execution. But this is just a feeling.Actually i don't know where i may have errored.
Short answer: You have to set the session or token or whatever you use for authentication on the client side.
Long answer:
You have the req object and the res object available in your handlers. The req object is the request and data sent by the client (web browser). The res object is what is sent back to the client as a response. As soon, as you call any terminating method on the res object like res.render or res.send, the handling of the specific client request is done and the linked objects are "thrown away". Therefore, setting any value on the req object does never affect the client, as the req object never gets sent back to the client. Modifying the req object would only be of use if you want to access some values on the req object in another middleware dealing with the same request.
So, you have to send the user id in the response object back to the client upon successful authentication and on the client side, take this id and set it e.g. as permanent header to be sent along with every request sent from the client from now on.
Hint: This way of authentication is not very secure. Usually, you would generate a token - e.g. a JSON Web Token (JWT-token) on the server-side upon successful authentication, send this token to the client and verify this token on each subsequent request.
Hint 2: Search (on Stackoverflow) for e.g. "node.js authentication" to get more input.
I am configuring a server using express.
My question has nothing to do with the project itself because it is running great.
I just have a minor doubt about why I have to use GET when for me it makes more sense to use POST.
So, for short I am configuring an API key on the server side and fetching it on the client side so I can use it.
This is the snippet on the server side:
const apiKey = process.env.API_KEY;
console.log(`Your API key is ${apiKey}`);
const dataObject ={};
app.get('/api', (req,res) => {
res.send({key: apiKey})
})
app.get('/all', sendData = (req,res) => {
res.send(dataObject)
})
app.post('/addText', (req,res) => {
let newEntry = {
agreement = req.body.agreement,
subjectivity = req.body.subjectivity
}
dataObject = newEntry;
res.send(dataObject);
} )
And then on the client side I fetch on the '/api' path:
const getApiKey = async () => {
// Getting API key from server
const request = await fetch('/api');
try {
const data = await request.json();
console.log(data);
return data;
}catch(error) {
console.log('ERROR', error);
}
}
Ok, that's working and everything, but my question is:
On the first GET on the server side, I understand that I am sending the API key to the '/api' path so that I can retrieve this key with fetch on the client side. But if I am sending the api key to this path, why am I using GET and not POST?
Sorry if it seems a stupid question but I am having a hard time understanding the GET method.
Thanks!
You are not sending any API key to the server. The server is sending the API key to the client as a response. The client uses a GET request to get the API key from /api. The names of the methods (GET, POST, PUT, DELETE, ...) are from the perspective of the client.
"And then on the client side I fetch on the '/api' path:" No. First the client sends the request with
const request = await fetch('/api');
try {
const data = await request.json();
console.log(data);
return data;
}catch(error) {
console.log('ERROR', error);
}
This triggers the callback in
app.get('/api', (req,res) => {
res.send({key: apiKey})
})
and the server sends the response.
This code returns the API key from the server. It does not send it from the client to the server.
app.get('/api', (req,res) => {
res.send({key: apiKey})
}
The
res.send()
Function is constructing the response returned by the server to the client.
Usually, you use the GET method when the client has to read some data from the server, in this case, you want to read the API_KEY defined in the server. GET has no body, but every request may be parametric by passing parameters in the query string.
On the other hand, when you have to update or create an entity on the server, that's when POST (or PUT) enters into action. Here you pass your data in the body of the request.
I am using postman to mimic requests to firebase and firestore, i am using FBAuth middleware for protected routes like uploading images, posting a comment, so i need to make sure that the user is authenticated before posting an image or writing a comment, but i always get a message in postman that my id token has expired,
FBAuth middleware:
const FBAuth = (req, res, next) => {
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
console.error('no token found');
return res.status(400).json({ error: 'unauthorized' })
}
admin.auth().verifyIdToken(idToken).then(decodedToken => {
req.user = decodedToken;
console.log(decodedToken);
return db.collection('users').where('userId', '==', req.user.uid)
.limit(1)
.get();
}).then(data => {
req.user.handle = data.docs[0].data().handle;
return next();
})
.catch(err => {
console.error(err);
return res.status(403).json(err)
})
}
Then use the middleware like this:
app.post('/user/image', FBAuth, uploadImage);`
In post man, i am using the token i get from sign in process to make the request, but i always get that message:
{
"code": "auth/id-token-expired",
"message": "Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token."
}
The error message is telling you that you have a problem on the frontend, not on the backend. Your frontend has simply delivered an expired token. It will need to keep refreshing the token every hour, since that's how long they last.
You haven't really said anything about your frontend at all, but it should be using an ID token listener to get fresh tokens delivered every hour. If the client is JavaScript, it would use onTokenIdChanged. If you're copying the token from your web or mobile client for use in postman, know that you will need to keep generating and copying a new tokens during development in order to stay fresh every hour.
When we use jsonwebtoken in Node, we sign a particular token for the user and send it back. However, when we verify the token when the user sends it in the header (Authentication: <token>), how does the jwt know that that token which it is verifying is for that particular user and not for some other user who also sent a request at the same time? Does it store the token somewhere internally?
At the time of login, you sign a token where payload is the userId, which is nothing but the _id field in the queried user object.
loginUser: async (req, res, next) => {
try {
const { email, password } = req.body
const user = await User.findOne({ email })
const token = auth.signToken({ userId: user._id })
res.status(200).json({ user, token })
} catch (error) {
return next(error)
}
}
auth.js
function signToken(payload) {
return jwt.sign(payload, JWTSECRET)
}
function verifyToken(req, res, next) {
const token = req.headers.Authorization || req.headers.authorization || ""
if (!token) {
return res.status(403).json({ error: "Not authorized" })
}
jwt.verify(token,JWTSECRET, (err, decodedObj) => {
if (err) {
return res.status(403).json({ error: "Not authorized" })
}
req.user = decodedObj
next()
})
}
module.exports = { signToken, verifyToken }
In the callback of jwt.verify, you get a decodedObj which is like:
{ userId: '5edb3ae6d6b129183c1393bc', iat: 1591425786 }
where iat is the time at which the jwt was issued.
req.user = decodedObj
Here, we're "attaching" the decoded data to the user object so that when a request is made to the protected route, we can get the userId from the request object, like req.user.userId, and then query it from the database.
When you sign a token, you provide a payload which can be a userId and a secret. So, the token gets signed. After that, you need to verify the token in case you're trying to access some protected page that requires a token.
So, when you send a request to that protected route, like this:
router.get("/me", auth.verifyToken, usersController.identifyUser)
where identifyUser is a controller function that just identifies the logged in user by checking the userId(remember the user object contains the decoded object data).
how does the jwt know that that token which it is verifying is for that particular user and not for some other user who also sent a request at the same time? Does it store the token somewhere internally?
It's because of the payload that you give, which is unique to the user.
The Authentication token is stored in an Authentication Server, so when you send the Authentication token in your request header, the Authentication Server authenticated your client.
After being authenticated by Authentication Server, the client can now pass JWT to make API calls to the Application Server. Since client is allowed to make API calls, Application Server can verify the JWT token the client has sent and can process the API call.
Note that for making API calls, the client has to send a Authorization: Bearer <token> for each API call, which is stored at the server (aka Authorization Server)
the token is most store in the client
when the token verifying successfully, we will get some user info, etc account id, so we can use account id to find more user info in the database, and check the use is really exist
maybe it is useful for you?
You will typically sign the token with the user id when sending it from the server. So when the client then sends back that token you decode it and it will return the id to you. Which you then use to find the user in the data base
I am building my application using Angular and node.js (express) and I am using JWT for authentication and authorization. Currently, I am storing JWT in session object on server in memory (req.session.jwt = user.generateToken()) and also, I am sending refresh token in HttpOnly cookie to the client.
When JWT expires, I want server to give me new JWT, if refresh token from cookie is equal to the refresh token in database - asociated with user.
I tried to implement refresh token logic in my auth middleware in the catch block, but it did not work.
(If token validation failed, then compare RF token from cookie to RF in database, if true, set new JWT and run the routehandler.)
module.exports = function(req, res, next) {
const token = req.session.jwt;
if (!token) return res.status(401).send("Access denied.");
try {
const decoded = jwt.verify(token, config.get("jwtPrivateKey"));
req.user = decoded;
next();
} catch (ex) {
(async () => {
let user = await User.findOne({ username: req.session.username });
if (!user) return res.status(500).send("Invalid session");
if (user.refreshToken === req.signedCookies.refreshToken) {
req.session.jwt = user.generateToken();
next();
} else {
return res.status(401).send("Invalid session");
}
})();
}
};
I had implemented this refresh token logic, when I was storing token and refresh token in localstorage on client, but this was not great implementation, so I tried to make it like this.
I had token.interceptor in my Angular part of the project - from this tutorial (https://angular-academy.com/angular-jwt/#refresh-token)
Here is my github repo of the backend https://github.com/TenPetr/dashboard_backend
If you have any idea, how to implement this logic, please let me know.
Thanks for your advices.
When you generate a new JWT token, as the request is going to continue to next middleware step, probably you will need to set the user, same as you do when the jwt is correct.
Something like this, try it, and let me know if it helps:
if (user.refreshToken === req.signedCookies.refreshToken) {
req.session.jwt = user.generateToken();
const decoded = jwt.verify(req.session.jwt, config.get("jwtPrivateKey"));
req.user = decoded;
next();
}