I have a simple web application that a user can create an account and login ... whenever a user log in i am generating a json-web-token for him and storing it on his database object ... im using that token to authenticate the user when he visits certain routes ( Bearer token on the header ) but the problem is that when the user refreshes the page i lose that token and he have to provide the email and password again to generate another token ... i was thinking about using localStorage or cookies but maybe there is a better / commonly used way for this ... any advises would be helpful thanks .
router.post('/user/login' ,async (req,res)=>{
try {
const user = await User.findByCredentials(req.body.email,req.body.password)
const token = await user.generateToken()
res.send({ user, token })
} catch(e) {
res.status(404).send(e)
}
})
axios({
method: "post",
url: "/api/user/login",
data: {
email: email,
password: password
}
})
The traditional way to use Persistent session(remember me feature) is to use cookies.
You can make set the max age lets say 30 days when logging in.
router.post('/user/login', function(req, res) {
....
req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // Cookie expires after 30 days
....
});
The logical flow should be:
When the user successfully logs, a login cookie is issued.
It should contain some kind of token which you then need to HASH(hash(cookie)) and store it in DB.
When a non-logged-in user visits the site and presents a login cookie, the series identifier is looked up in the database.
If it finds the cookie it gets authenticated, otherwise asks to login.
Start the session on the first line of your page, and store the login credentials in it. You can do the same for all other pages to be accessed after login.
Related
For example, when I log in I create an access token with payload { 'userId': 1, '2fa': false} (/login route), then I do another route '/login/auth' that checks for example if a one time password is correct, if it is I created another jwt but this time with 2fa set to true. Then proceeding routes will check whether 2fa is true to run. If not, it will error. Is this a proper way to do this or hacky?
// middleware
const auth = (req, res, next) => {
const token = req.header('access-token');
if (!token) return res.status(401).json('Not Authorized');
try {
const payload = jwt.verify(token, 'secret1');
req.payload = payload
next();
} catch (ex) {
res.status(400).json('Invalid.');
}
};
// routes
router.post(
'/login',
async (req, res) => {
try {
/* login database stuff goes here if successful creates access token
*/
const token = jwt.sign(
{ userId, twoFactorAuthenticated: false },
'secret1',
);
res.status(200).json(token);
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
router.post(
'/login/auth2',
auth,
async (req, res) => {
try {
/* verifying two factor auth logic goes here
* if succesful approves the 2fa
*/
const token = jwt.sign(
{ userId, twoFactorAuthenticated: true },
'secret1',
);
res.status(200).json(token);
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
router.post(
'/some-route',
auth,
async (req, res) => {
try {
if(!req.payload.twoFactorAuthenticated)
return res.status(400).json('user has not completed second factor auth')
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
My use case doesn't really require login authorization but for something different but same concept applies.
The process you are suggesting will work, but it can be improved.
Set JWT claims
Since the payload of JWT is not encrypted, it is only meant for storing non sensitive that is usefull in the verifing process. Although the payload is not encrypted, it is signed and cannot be changed without knowing the secret or private key.
There are a number of standard claims which might be useful, besides your own custom twoFactorAuthenticated claim. You always should set an expiration time (exp) on your tokens. For the userId you may use the subject claim (sub).
Use middleware for authentication of routes
Once the two factor authentication is done, you want to check the JWT on all protected routes. Now, you have put the check in the ‘some-route’ controller itself, but it is preferable to define middleware for this if you have more then one protected route.
Use a http only cookie to store JWT with a short life time
In your solution you send the token to your client and let the client add it to the authentication header. This probably means that the token will be stored in local storage. It may be more secure to use a secure http cookie for this, with a short life time, like 10 minutes or so. This means that if the token is compromised, your API is vulnerable for a maximum of 10 minutes. For a user, having to authenticate every 10 minutes is a horrible UX. So that’s why you may want to implement refresh tokens.
Use refresh token and use it only once
In your example, after the twofactor authentication is verified, you send a token back with the twoFactorAuthenticated set to true. As suggested before, you can send this in a secure cookie with a short life time instead. At the same time, you generate a refresh token with a much longer expire time, e.g. 4 hours. When the cookie expires after 10 minutes, the client can use the refresh token once to get a new cookie and a new refresh token.
Important:
The refresh token can only be used to get a new cookie, not on other routes. It can be stored in local storage and send in the authentication header as a bearer token.
The refresh token may only be used once, so it needs to have a unique id and you need to store the id on the server side. If a refresh token is used you set it to invalid.
If a refresh token is used, you check if it is not used before. If it is used for the second time, something strange is happening, because with a new cookie, the user also gets a new refresh token. At that moment you just set all valid refresh tokens of the user to invalid. Than yo are sure they have to log in again on all devices, after their cookie expires.
This requires some implementation on client and server side, but I think it makes your authentication process much more solid.
You can use '2fa' boolean value in payload, it does not show any secret information. It is impossible to create this token without the JWT secret key which is private to you. But you can do this with only 'userId' as well, store '2fa' value in user table and check the value when you verify the token.
Let's say that after initial launch/login, the backend sends a token to frontend containing user info such as username, email, and other credentials. This token resides in user's client and gets sent back with every API call for authentication.
At one point, the user might update their email. From then on, JWT should be regenerated so that it contains new email instead of the old one.
I can achieve this by fetching most recent data from the DB and generating a new token on every 'verifyAuth' call and it works mostly fine, but I believe a more efficient flow can be implemented.
The 'verifyAuth' middleware that I use is almost a global middleware, it is executed with almost every request and multiple times on that, so fetching data on every call significantly increases response times.
How can I make sure that JWTs are up-to-date efficiently without repeated DB queries?
const verifyAuth = async (req, res, next) => {
const { authorization } = req.headers;
if (!authorization) {
return res
.status(status.unauthorized)
.send({ ...error, message: 'No auth.' });
}
try {
const token = authorization && authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const { userId } = decoded;
const dbResp = await db.oneOrNone(
`SELECT id AS user_id, username, email
FROM users
WHERE id = $1`,
[userId]
);
req.user = {
accessToken: token,
userId,
username: dbResp.username,
email: dbResp.email,
};
next();
} catch (e) {
return res
.status(status.unauthorized)
.send({ ...error, message: 'No auth.' });
}
};
router.use('/api/app', verifyAuth, AppRouter);
router.use('/api/user', verifyAuth, UserRouter); // and more routers like this.
How can I make sure that JWTs are up-to-date efficiently without repeated DB queries?
The short answer is - you can't. Once a JWT is issued, the only way to check whether it contains stale data is to verify that data with a source. But there are ways of getting around this problem:
You can keep the expiration of your tokens short. If your tokens expire after 5 min then this is the maximum amount of time that you will have to deal with stale data. Maybe you're ok with that.
Cache your calls to the DB. If you call the DB a few times during one request with the same query you can cache the results. You probably don't even need some fancy caching mechanisms, you can just keep the result of the query in memory and use it for subsequent calls to the DB.
You can implement a feature where you will keep a track of when a user's data has changed. For example, you set up a redis database (which is a fast key-value store) where you keep information "userId - timestamp of last data change". Then when you validate a token you can check whether it was issued before that entry you have in Redis. If true, then you call your DB for new data. Calling Redis will be much more lightweight than calling your SQL DB.
I have a standard LocalStrategy Passport.js authentication strategy. During this, the LocalStrategy checks the password against the database using bcrypt, and authenticates the session. In case you need to see the code:
const LocalStrategy = passportLocal.Strategy;
const userStrategyHandler = async (username, password, done) => {
const [ userFetchErr, user ] = await to(userService.findByEmail(username));
// ...
const authResult = await authService.comparePassword(password, user.password);
// ...
return done(null, user);
};
const userStrategy = new LocalStrategy(userStrategyHandler);
passport.use(userStrategy);
However, when they sign up, I send an email that verifies the user's account. When they click the link, it currently redirects them to the homepage. They are not logged in, which is not a great UX, but there's an opportunity before the redirect in the /verify route to potentially log the user in
Is there a way to authenticate the user on the server-side, without requiring calling the Passport LocalStrategy? That way I could log the user in the one time upon verification. After that, the user will authenticate using their password. Or is there another way to achieve that result?
for this situation, you should create a link like below based on jwt and send to use email:
https://website/verify#token=eyJpZCI6Nzk5OTEwNzc3NjMyMzI1NjU0LCJlbWFpbCI6Im0ueS5haG
this token should store in a new table that have two column, token and status, status first of all is true after click on the link in email, /verify route called ,and the status will be false and becomes invalid for the token, so if the token valid and status true you can redirect user to homepage, without passport you can do what you want
``
It was actually very easy to resolve this. I added another column (isInitialVerification BOOLEAN NOT NULL DEFAULT FALSE). Then, on the /verify route, I set isIntialVerification to true. In the Passport LocalStrategy, I simply checked for the column value and then reset it to false.
Make sure you throw an error (i.e., return done(err, ...)) if you cannot update the isInitialVerification value back to false, to avoid the potential of permanent authentication - the worst case is they'll have to manually log in.
The above is incorrect. Anybody could send the request between the isInitialVerification and the next request, and be authenticated. See Mohammad's answer for a possible solution instead
Hi I want to implement a Log In with Spotify feature in my website but I don't want to redirect users to a different page, I would like to just open a popup window. An example of the behavior I want is found at https://developer.spotify.com. There when you click on log in, a pop up window is opened so you can log in with spotify without any redirect.
That's how Spotify Developer website does it:
Open a popup window to /api/authorize. Once the user has allowed the application, it will redirect him to the callback page.
On the callback page, use the returned authorization code (GET parameter code) to generate access/refresh tokens by doing a POST request to /api/token (check out the documentation). This should be done on server side because it requires sending client ID and client secret keys.
Store the access/refresh tokens in the localStorage and close the popup.
Detect close event, get the tokens from the localStorage and use them for the API.
Example
Login page:
// Open the auth popup
var spotifyLoginWindow = window.open('https://accounts.spotify.com/authorize?client_id=REPLACE_ME&redirect_uri=REPLACE_ME&response_type=code');
// Close event
spotifyLoginWindow.onbeforeunload = function() {
var accessToken = localStorage.getItem('sp-accessToken');
var refreshToken = localStorage.getItem('sp-refreshToken');
// use the code to get an access token (as described in the documentation)
};
Callback page:
// Assuming here that the server has called /api/token
// and has rendered the access/refresh tokens in the document
var accessToken = "xxx";
var refreshToken = "xxx";
/////////////////////////
// Store the tokens
localStorage.setItem("sp-accessToken", accessToken);
localStorage.setItem("sp-refreshToken", refreshToken);
// Close the popup
window.close();
Following up on Teh's response above. If you don't want to use localStorage, I registered a global window function and simply passed the token as a payload back to parent window. Works well for a pass-through experience like saving playlists.
Popup:
popup = window.open(
AUTHORIZATION_URL,
'Login with Spotify',
'width=800,height=600'
)
Callback Function:
window.spotifyCallback = (payload) => {
popup.close()
fetch('https://api.spotify.com/v1/me', {
headers: {
'Authorization': `Bearer ${payload}`
}
}).then(response => {
return response.json()
}).then(data => {
// do something with data
})
}
Callback Page:
token = window.location.hash.substr(1).split('&')[0].split("=")[1]
if (token) {
window.opener.spotifyCallback(token)
}
I wrote about this technique in more detail on Medium.
I’m quite new to backend development…
With using my API I would like to be able to display a list of users and also indicate if they are currently logged in. I got the basic authentification working using passport and json web token
I’m not looking to get the current logged in user.
I want to be able to retrieve a list of users and see if they are logged in or not.
Like this:
var users = Users.find({});
// console.log(users) output:
{
name: 'foo'
password: ...
isLoggedIn: false
},
{
name: 'bar'
password: ...
isLoggedIn: true
},
{
name: 'baz'
password: ...
isLoggedIn: false
}
isLoggedIn would be set to true if the user is currently logged in and to falseif not.
How can I do that? Thank you!
It sounds like what you would like to do is update your MongoDB database based on login/logout events. To do this you could use something like mongoose to work with your Node backend to easily access your database in MongoDB.
You can include mongoose after installing with npm install mongoose like so:
var mongoose = require('mongoose');
var User = mongoose.model('User');
Note that User corresponds to whatever schema you create for storing user information.
Assuming you have some sort of router object for handling requests, you could construct route handlers for /logout and /login and use your imported mongoose User model to retrieve and then modify a specific User object as such:
// whenever user goes to '/login' (you can have, say, your 'login' button make a request to this URL
router.get('/login', function(req,res) {
// your authentication here; passport stores the currently authenticated user in req.user
var username = req.user.name; // here we assume the username is stored as 'name' as you have in your code but change this based on your schema
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = true;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to some page here maybe
}
});
});
});
// whenever user goes to '/logout' (you can have a logout button make a request to this URL
router.get('/logout', function(req,res) {
// currently authenticated user is still in req.user
var username = req.user.name;
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = false;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to login/register page maybe
}
});
});
});
So to summarize what this code would do:
based on the url a user would go to, our route handler would fetch one correct, unique User object from our database based on the name (username)
it would do so by accessing the username property of req.user which corresponds to the currently authenticated user with Passport, which, again will be different for all users
update the field that we use to keep track of login status (isLoggedIn)
and then save the changes, after which we are done updating the state to reflect whether the user is logged in or not, so we can now redirect to some other page or display other content
Finally then, you could retrieve a list of all users similarly to your code like so:
User.find({}, function(err, users, data) {
// all users from your database are in `users`
console.log(users);
});
Edit for expired sessions:
So, to track expired sessions, since you're using Passport, would in theory require functionality to signal with some sort of event / callback / message, etc. the moment the session is deemed invalid. Now that is tough to monitor and from my experience with Passport, stuff like that isn't implemented in all authentication strategies and might vary based on the strategy to be used by developers (think for instance if a browser window is closed, based on Passports authentication strategy, or just browser, it might destroy the cookie for the session right away and our server has no way of knowing about it). I do recommend checking out all the authentication strategies Passport offers in case there are some better ones here.
Now, if you would like to add functionality to track the users passive login/logout status with sessions yourself, you could use something related to cookies. Again, not necessarily one to use, but here's a couple handy Express modules: cookie-parser and cookie-session.
Then, you could set and read cookies like this, using cookie-parser:
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
You would put this code somewhere right after the user is authenticated:
// cookies are stored here
console.log(req.cookies);
// configure your cookie
var options = {
expires: 1000 * 60 * 60, // expires after one hour
httpOnly: true
}
// Set cookie
res.cookie('session', ('user-' + req.user.name), options);
And then, on the client side check if that cookie is valid continuously on some time interval, and if it expired Date.now() > cookie.expires then make a GET request to /logout, and there log out the user (currently still authenticated) by updating MongoDB and all.
However, since this would require making a mechanism to basically simulate an expired session, I would recommend using something analogous to a timeout, which would be much easier to implement. Just a note, this is sort of analogous to mechanisms on some pages you might have encountered where you get a pop-up saying 'You will be logged out due to inactivity'. In your main.js or whatever client-side script define a function to keep going on a time-out, unless the user does some action.
var inactivity = function () {
var t;
// user doing something on your page, so keep resetting time counter when events happen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
// this is a callback function that will get called once a time-out countdown is done
function timeOut() {
// make a request to '/logout' here and logout the current user (you still will have access to req.user from Passport)
// also can redirect from back-end route handler to the login page for instance
}
// this gets called whenever an event happens, resetting the counter of sorts
function resetTimer() {
t = 0;
t = setTimeout(timeOut, 1000 * 60 ) // set this to however long you should wait to log out your user time (in milliseconds)
}
};
So basically what this approach would let you do, is automatically invalidate sessions yourself, which means you would have much greater control over updating the state of your database and logging users out.
Hope this helps!