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
Related
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.)
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.
First, let me give you an overview of my app, it is fairly simple.
The basic idea is that a user enters his accountname and password into an html form on the /Login page like:
<form action="/Login" method="post" target="_blank">
<label for="fname">First name:</label>
<input type="text" id="name" name="username"><br><br>
<label for="lname">Last name:</label>
<input type="text" id="password" name="password"><br><br>
<input type="submit" value="Submit">
</form>
This works as expected, it makes a post request to /Login transferring username and password.
When the data gets to the server, the server checks if the username and password are valid, if they are a new JSON web token will be created. I use the npm library "bcrypt" for the verification of the account and "jsonwebtoken" for the creation of the token. This looks like this:
const accessToken = jwt.sign(user, process.env.JSON_TOKEN_SECRET, { expiresIn: "10m" })
where the user is just an object, with an id key and a value whatever the name of the account is. If the username was admin it would look like this:
const user = { id: "admin" }
So now I want to deliver this token to the client and I do not know exactly how. I have tried:
res.json({ accessToken })
But all this does is displaying the JSON on the screen. I have also tried a regular res.send() but it won't deliver the token correctly to the user. I want a webtoken authentication so I can use protected routes in my project. If I want to visit a protected route, I get an error, that there is no authentication header, which probably means that the browser didn't receive the token correctly. The route protection is just basic middleware:
const authHeader = req.headers["authorization"] //if console logged authHeader is undefined
const token = authHeader && authHeader.split(" ")[1]
jwt.verify(token, process.env.JSON_TOKEN_SECRET, (err, user) => {
//Some error handling
next()
})
Next, I tried using Postman, where I made a POST request to /Login and got my token in the body of the response back. I pasted the token manually in a Bearer Authorization header and made a GET request to a protected route, which worked.
So how can I send the webtoken correctly to the client?
When you login with POST request it always send token in response so use some function to store that response in localStorage or sessionStorage or after successfully login you could send other request for getting a token from express server.
when user signs in you create json web token. You have a login controller.
const postLogin=(req,res)=>{
// you read the req.body, get username,email, password etc
// then you use email to see if user exists in db
// if user exists, then you verify the password
// if everything works so far you create the token
// token is kinda locked version of your inputs
// when you send it to server, only that server unlocks it with secret-key
let token;
try {
token = jwt.sign({ _id: this._id, email: this.email }, "this-is-secret", {
expiresIn: "1h",
});
} catch (e) {
throw new Error(e.message);
}
res
.status(200)
.header("x-auth-token", token)
.json({ token, userId: existingUser._id.toString() });
}
x-auth=Extended authorization
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.
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();
}