I'm using express-session, connect-redis and ioredis to store the session and the user will have the redis key stored in a browser cookie.
connect-redis resets the TTL every time the server is being interacted with, which means that right now with default settings the in-memory session will outlive the browser cookie at some point as the user interacts with the website. I would like to keep the user session alive and not show a popup to the user specifying that they should reauthenticate or that they will be logged out soon or something like that. How do big sites such as YouTube and Facebook keep user sessions alive? I've never experienced the sudden need to reauthenticate on youtube in years I think. So my question is how should cookie session expiration dates get handled with an emphasis on good user experience, and not sacrificing security?
My current idea is to simply just check when there is a certain amount of time equal to the time left before a cookie expires, and if that turns out to be true AND the user has just interacted with the website, reset the maxAge property on the cookie. In that way, if the user isn't going to interact with the website for more than that certain amount of time say 3 months, and the time left before the cookie expires is exactly 3 months, then the user will get logged out the next time the user visits the site after those 3 months. In other words, as long as the user keeps doing something on the website in intervals of less than 3 months, then they will always still be logged in and authenticated. What do you think about that? I could also set the rolling property to true, but I don't want to send session cookies with every authenticated request because of performance reasons. Also, I can't write to the cookie trying to increase its expiration date with client-side Javascript since I'm setting httpOnly to true.
To be honest, it's much easier then that. Imagine you have a remember me when you sign in, if that was untoggled, you'd change TTL to a day or few hours when session is created or you use the TTL you set by default.
You definitely want the sessions to forcefully expire, so these keys change up all the time and make any hijacking unlikely.
Below is how I set my redisStore up, I am lacking sync between two devices wanting a single account session but that's a topic for another time.
WEBAPP.use(SESSION({
name: "WebSiteNameOrCookieName",
store: new redisStore({
client: client,
ttl: 604800,
disableTouch: true
}),
secret: "your_secret_key_make_it_hard_as_hell",
cookie: {
domain: ".example.com",
sameSite: 'strict',
secure: true
},
saveUninitialized: false,
resave: false
}));
Related
I'm running a website + native apps that communicate via HTTPS with my backend. The following requirements must be fulfilled:
Sliding session on the website. That means if the user interacted with the website within the last xx Minutes, he must not be logged out
Remember me on the website. When this is checked, the user must not be logged out (or after a very long time)
The user must not be logged out on the app
Access can be revoked, either by the user (currently logged in) or specific events (password changes).
What I currently have is the following: A refresh token endpoint generates a JWT when password hash and username match in the database. Every refresh token has a jti that is stored in the database, as well as expiration (for DB cleanup only), device_id and a revoked flag.
Another endpoint can be hit with the refresh token, which returns a JWT access token. The access token is valid for 15 minutes. The access token cannot be revoked.
My problems arise with requirement 1. I do not want the user to reauthenticate when he's interacting with the website. This means I need the refresh token. However, the refresh token must only be valid for e.g. last user interaction + xx Minutes. I cannot extend the access token with every request, as there is no way to blacklist access tokens. This would mean that a leaked access token is like a master key forever (as long as you constantly hit the api in 15-minute intervals). But I also do not know what the expiration for the request token could be.
The second problem is (2) with incognito modes or multiple devices. Assuming the user opens 20 private tabs and uses remember me on all of them. Then I have to store 20 tokens in the database. I could, of course, set a limit for type "web" to say 5 and "app" to 3 and remove the oldest last accessed one from the database (and therefore invalidate it). But this would log him out on the "main" browser if he opens 5 private tabs somewhere. It would also limit the number of phones to e.g. 2.
Different PCs/laptops would also generate many refresh tokens of type web. How would I best identify the corresponding device so access can be revoked, but I also do not store hundreds of refresh tokens over the application's lifetime? Best would be one refresh token per device (windows+firefox, iPhoneA, iPhoneB, windows2+firefox). But identifying desktop PC's is super hard.
What it comes down to is:
How can I store refresh tokens in the DB so they are identifiable to the end-user (e.g. Whatsapp webs "Safari started in New York last used at xxx-xxx-xxx"
How do I avoid having hundreds of tokens per user in the DB (as refresh token basically never expire, and the user can open as many private tabs as he likes without logging off)
How can I implement sliding windows with the refresh/access token pattern? So no unlimited refresh token on the client-side, but also no logoff after the access token expires as long as there is any usage. I could have it in the session storage, but then it still clutters my database and shows to the user as "currently logged in" (which displays all refresh tokens) as it's basically still valid.
Sliding session on the website. That means if the user interacted with the website within the last xx Minutes, he must not be logged out
To solve this problem you can use a refresh token, i.e when the user login for the first time, the application will return a access token (JWT format), with an expiration date set to the amount that you want.
When the user will browse the application, your backend will return a X-Refresh-Token header valid for your xx amount of time (i.e you'll need to return this header for each backend call).
If the acess token is expired (the backend will read the access token used, and perform check on expiration date token field), the backend will return a 401 Unauthorized error,
and your client must call the authentication endpoint, providing the last refresh token stored, to issue a new access token.
With this implementation your requirement #1 is satisfied.
Remember me on the website. When this is checked, the user must not be logged out (or after a very long time)
To solve this one, you'll just need to generate a long lived access token (i.e with an expiration date set to the amount of time you want).
The user must not be logged out on the app
I don't understand this one
Access can be revoked, either by the user (currently logged in) or specific events (password changes).
This one is really tricky. Since backend is stateless, revoking access token is a really complex topic.
Hopefully a lot of pattern existing to solve this one, we just need to discuss about it.
I'm attempting to extend my Identity Server 4 implementation and provide an in house password reset feature. I've completed the entire password reset process however I'm running into a situation upon redirecting the user back to the client application that's causing me grief.
If there is a valid existing authentication cookie, for instance if a previous user doesn't initiate a proper log out, when I redirect the current user to the client application, the middle ware uses the existing cookie to build the principal which inevitable ends up with the current user being treated as the previous user, which makes sense.
In vain I attempted to make a call to sign in the current user post password reset in Identity Server via await HttpContext.Authentication.SignInAsync(...) as shown during login on their samples here with no change in results.
It seems as I'm missing something fundamental in how to properly reproduce the sign in process from within my password reset process.
Is there any way from Identity Server that I can effectively sign in the current user post password reset and force the client application to invalidate/ignore the existing authentication cookie?
Update: After thinking about this over the weekend, I guess this broaches a much broader subject in that how far does one go in ensuring the invalidation of existing auth cookies? The fact is that because of the way cookie authentication works, as long as that cookie remains valid we are under the assumption that we are is still receiving requests from the original physical person.
One might argue that this is a good case for reducing the expiration time of the cookie, but that's a slippery slope because on one hand if the expiration is too short, usability can be affected making for a less enjoyable experience and unhappy users. Make the expiration too long and that window where a valid auth cookie is sitting on the browser idle with no meat and bones behind it grows, and all it takes is someone else to browse to the site to proceed unhindered and authenticated as someone else. This would be most evident in a situation with shared computers where many people need to access the same app.
I suppose a secondary question would be to what extent is the onus on the user to initiate a proper logout process, and to what lengths should I go to to ensure any existing auth cookies are invalidated?
After a few conversations with co workers, the consensus was that it is the responsibility of the user to ensure they are properly logged out in any public/computer sharing situations and that we could not guarantee without a reasonable doubt any previously existing authentication cookies are cleaned up without greatly affecting user experience and basic site functionality.
For due diligence we however will provide some manner of attempting to clean up any existing authentication cookies by doing a simple check and redirect on the default route of the client application. This solution has nothing to do with identity server, and is not fool proof, but in conjunction with tuning the session expiration times should prove effective for daily users.
[AllowAnonymous, Route("")]
public async Task<IActionResult> Index()
{
if (User.Identity.IsAuthenticated)
{
await HttpContext.Authentication.SignOutAsync(...);
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
}
return RedirectToAction(nameof(Welcome));
}
[Authorize]
public IActionResult Welcome()
{
return View();
}
As you know, there are some good reasons for using token based authentication instead of session based.
In session based, of course there is a expiration time. So if user is not active for a while, his session get expired. But before expiring, if he send request to server, his time will be extended.
There is an awesome tutorial here about JWT. I have a question about expiration time for token. Imagine we set the expiration time to 100 seconds, then we sign the token. It doesn't matter user is active or not. After 100 seconds that token will not be valid anymore. This bothers the user. Is there any way to extend the time?
Is it a true approach, or maybe I have a mistake. Any idea?
If I understand the question correctly, it is fairly simple to alter the expiration of a JWT token during creation...
The "exp" (expiration time) claim identifies the expiration time on
or after which the JWT MUST NOT be accepted for processing. The
processing of the "exp" claim requires that the current date/time
MUST be before the expiration date/time listed in the "exp" claim.
More information can be found here https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4
Basically the exp key takes a unix timestamp - set the timestamp to > 100 seconds from now and you will accomplish your goal.
To "refresh" the token your API needs a service that receives a valid, JWT and returns the same signed JWT with the updated expiration.
Silent refresh
There are 2 major problems that users of our JWT based app will still face:
Given our short expiry times on the JWTs, the user will be logged out every 15 minutes. This would be a fairly terrible experience. Ideally, we'd probably want our user to be logged in for a long time.
If a user closes their app and opens it again, they'll need to login again. Their session is not persisted because we're not saving the JWT token on the client anywhere.
To solve this problem, most JWT providers, provide a refresh token. A refresh token has 2 properties:
It can be used to make an API call (say, /refresh_token) to fetch a new JWT token before the previous JWT expires.
It can be safely persisted across sessions on the client!
Here a brilliant exhibition in HASURA BLOG--> https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/
You didn't give further information, but I'll assume you are going to use JWT for web-browser authentication.
you can save your JWT in a cookie with httpOnly and secure attribute and set cookie expiration time long enough(maybe 1 years) and inside of your JWT claims set exp property to a shorter time ( maybe 1 week or something else). now in every request the cookie will be sent to the server so you can check for expiration time.
something like this :
if(decodedJwt.exp < Date.now()){
//token is valid, do your stuff
}else {
//token expired, regenerate it and set it to the cookie
//also update the expire time of the cookie
}
I have a web application where I want users to only be able to use it from one location (meaning a user can't actively be using the application at two locations). Currently I got this working in a very common way by only allowing 1 cookie session to be valid and removing any existing ones when a user logs in. Unfortunately I've been told that my method of only allowing 1 cookie is unacceptable because my users move around a lot to different sites and are tired of having to login every time. An easy solution would just be to allow more than 1 cookie, but I can't do this because I need to make sure a user account is not being used at two locations at the same time.
I'm wondering what is the best way to implement a system like this where a user can't be active at more than 1 location, but shouldn't necessarily have to login at every location they visit.
One possible idea I had was to allow multiple cookies to be recorded, but once a cookie becomes active (meaning I notice that session navigating the application) all of the other cookies are locked out for a certain timelimit like 15 mins. If no cookie session has been active for 15 mins then allow any cookie to login and gain dominance over the others untill it exceeds the timelimit.
Edit: It's ok for them to remain logged in after they leave a location
One way to do this is to log their last ip address and at what time that access was. On each access, you can check their last access.
If the last access is from the same ip, let them through.
If the last access is from a different ip, check how long ago that was. You can then define a cut-off point for how long they need to be idle before they can access it from another location. 15 minutes seems reasonable.
All of this can be done on the backend and this would possibly provide a higher level of security.
The browser allows users to store their credentials. Let them use this feature to log back in without hassle.
No need for a timeout. Allow multiple cookies, but only one active one.
Instruct your users to close the application when they leave their workstations. Make this something that's easy to do. Put a close button on each page or perhaps catch onBeforeUnload and notify the server that the page is no longer being displayed. Do keep the session when the user closes the application, but mark it as currently inactive.
When you get a request with a cookie that belongs to an inactive session, activate that session without complaints if the user has no other session active.
If the user still has another session active, something fishy is going on. So remove all sessions and send the user to the login screen.
(That'll teach them :) )
Can someone tell me what the default timeout is when using activeRecord store?
I don't want to 'set' the timeout because I want it to behave as a session cookie.
ie: expire when the user closes the browser, which doesn't happen if you manually set the expire date.
When I leave the expire date off, the session will timeout sometime within a couple of hours of no use. Why is this so?
So really what I'm asking is, is it possible for the session to not timeout at all when the user keeps his/her browser open and only expire when he/she hits logout or closes the browser?
Keeping in mind:
the cookie doesn't get deleted if you specify an expiration on the activeRecord session_store when user closes browser.
I think there is two points here, Session Timeout and Page/HTTP Timeout from Web Server.
As far as I understand, ActiveRecord Store Session store doesn't timeout, unless the user moves away from the page.
Alternatively, if the Web Server decides after sufficient time of idle-ness to then drop the connection, which in turn negates the Sessions.