Next-auth (JWT) logging sessions - mongodb

According to Next-auth documentation as we are using credentials provider to connect to our users collection for usernames and passwords Next-Auth does not use the session database to check if a session is active.
If you use a custom credentials provider user accounts will not be persisted in a database by NextAuth.js (even if one is configured). The option to use JSON Web Tokens for session tokens (which allow sign in without using a session database) must be enabled to use a custom credentials provider.
I am wanting to add a _middleware that will allow me to store and check that the latest JWT sessions inside our session database matches the latest one that the user is currently using.
Reason being is that if I have two devices technically I would be able to login on both devices and at the moment their is no real way to discern if the user from PC2 is also login on PC1.
So my theory and not sure if this will work is to add the following.
callbacks: {
jwt: async ({ token, user }) => {
console.log("running JWT - because of custom login")
user && (token.user = user)
(ADD CODE HERE TO SAVE TOKEN & CHECK IF TOKEN IS LATEST TOKEN + VALID - INSIDE SESSION DATABASE)
(IF OLD-TOKEN IS NO LONGER VALID OR THE LATEST TOKEN LOG THE USER OUT)
console.log("TOKEN IS "+ JSON.stringify(token))
return token
},
session: async ({ session, token, user }) => {
console.log(JSON.stringify(session) +" / "+ JSON.stringify(token) +"/"+ JSON.stringify(user));
session.user.tokenID = token //ADD CODE HERE TO SAVE TOKEN TO SESSION COOKIE
session.user = user
return session
}
},
Then if I create a middleware that checks this tokenID and matches it with the session database and if it is the latest result from said user.
For example.
Say PC1 (user1) login here
{
_id: 1
tokenID: 918171-918171-81716-0887
userid: 00-00-00-001
expire: "2022-05-23T12:47:04.593Z"
}
But then PC2 also (user1) login again and created a new session
{
_id: 2
tokenID: 71888-651777-616666-0117
userid: 00-00-00-001
expire: "2022-05-24T12:47:04.593Z"
}
What I would need the middleware to do (which a simple mongodb query could do) is check if their is an older session stored for the same userID if so then logout from PC1.
Now there are a few things that I can see going wrong with this idea.
Other Provider Sessions (which use session DB) making it harder to validate
Every time you call the signup page or session it seems to re-run the JWT section - which in theory is fine, as we could use a findOne Update function which if token is in session then just update the expiry - however that would cause say PC1 refreshes after PC2 logged in then PC1 expire time might be longer then PC2 expire time (but a simple sort function would allow us to see if the ID was older then PC2 if so logout).
JWT changes token every time you reload the page
How would this help privacy and the user data?
By not storing the user details inside a session cookie we would not be exposing the data to hackers or other plugins like FB or Google as the user data would only be linked to a token ID. Which to request the user data you would have to make sure the tokenID was valid first and then be allowed to fetch user data.
I understand that Next-Auth may not want too do this, and this is why I ask the question what is the best practice to do what I am wanting to achieve.

This answer is based on the confirmation that the issue is that you want to be able to only have users able to be signed in to one computer/device at time and that you are using usernames and passwords to authenticate them.
In that scenario you also need to have a database that records a token in each JWT issued. It is not possible to solve for that problem without a database.
Here is how you can solve for it it using JWT and a database:
On every new sign in, you would need to use the jwt callback to add something like a UUID to each JWT and then record that UUID , the User ID and the time the JWT expires in a database.
In that callback, if there are other entries in the database for the same User ID you should mark them as invalid (or delete them from the database).
Everytime an existing JWT is read in that same callback you would need to check to see if the UUID in the database was still valid (i.e. still exist / doesn't point to a UUID that corresponds to a JWT flagged as expired) and if it is no longer valid, don't return a valid JWT.
You might also want to add special handling in the session callback that does something similar to improve the user experience by gracefully handling it in the User Interface of the computer they are being signed out of.
Effectively this has all the downsides of a JWT with all the downsides of a session database (although there are niche cases where this is a preferable approach).
I would not recommend using usernames and passwords or limiting users to only being able to sign in to one computer at at time, however given those unusually specific constraints (which also necessitates a solution that has a negative impact on performance) you may want to consider a different authentication solution and/or think how else you could address the underlying need this is trying to address (and if it's worth the cost and complexity).

Related

is it a good idea to put expired tokens to blacklist table?

To begin with this is how my current auth flow looks
User logs in
User gets a refresh_token assigned and stored in the database (long lived 7d)
Client receives an accestoken (Short lived, 2h), and stores it as a cookie. Client also receives the userId AES encrypted, and stores
it as a cookie.
As far as the access token is not expired, the user keeps using the token to navigate the website
The token expires
The expired access token gets send to a refresh endpoint, so is the userID (Aes encrypted) both currently stored in out cookies.
The server decrypts the userId and retrieves the refreshtoken that corresponds to the user by selecting the refresh token from the database using out userId.
Now we have in the server our refreshtoken and accestoken, so we refresh the token, and send back the new accesstoken. We also generate a new refreshtoken and overwrite our old refreshtoken in the database with the new one.
My question is basically related to that last step. Since those refresh tokens are still technically valid, since they have a long expiration time. Can I create a table in my database named "blacklisted_tokens" or something like that, and store there the values of the token? And then right before generating a new access token it should prior to that check if that refresh token is or isnt in that database, meaning that it will be blacklisted.
This is the authflow diagram
My question is basically related to that last step. Since those
refresh tokens are still technically valid, since they have a long
expiration time. Can I create a table in my database named
"blacklisted_tokens" or something like that, and store there the
values of the token? And then right before generating a new access
token it should prior to that check if that refresh token is or isnt
in that database, meaning that it will be blacklisted.
it's not recommended to do that as because, probability of generating 2 same token is low and adding NOT necessary additional processes to your back-end is not a good idea and has performance issue in large scale Token re-generation(a lot of users).
And also, Tokens are along with an identity(id) in which reduces security risks.
if i were you, i would just re-write new-token to old-token.
The most important type of cyber attack which threaten Tokens is The Sniffing attack and by doing below steps actually the probability of this attack goes almost to zero:
SSL certificate
Expiring Token and re-generation
Salty requests
Salt
In cryptography, a salt is random data that is used as an additional
input to a one-way function that hashes data, a password or
passphrase. Salts are used to safeguard passwords in storage.

Force Logout of user on multiple devices using JWT

We have an existing REST API that is currently using JWT.
Client came with the requirement that users can only use 1 device at a time. For example, if user logs in from iOS device then logs in Android device, then the iOS device should be "forced" to logout.
Since we are using JWT, we are not keeping track of tokens, except a Token Blacklist when user click Log Out.
I researched on how to "force" log out the user and it seems we would need to keep track of the last token used by the user, then invalidate that once we detect a new log-in.
Is there no cleaner / alternative way to achieve above?
Here are the steps to implement your requirement:
step 1: save the timestamp of last user activity performed for password update or logout from all device
add a column lastSessionResetDate to store last password update date in your user table
While performing forget password/change password apis or at logout-from-all-device, update lastSessionResetDate
step 2: set lastSessionResetDate in JWT Claims, when generating JWT token
example of setting claim while token generation
//make a claims map (`Claim extends Map<String, Object>`)
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, user.getUserName());
claims.put(CLAIM_KEY_AUDIENCE, "web");
claims.put(CLAIM_KEY_CREATED, new Date());
claims.put(CLAIM_KEY_LAST_SESSION_RESET, user.lastSessionResetDate());
//set claims and build JWT
return Jwts.builder().setClaims(claims).setSubject(user.getUserName())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + yourTokenValidity))
.signWith(SignatureAlgorithm.HS512,"yourSecretEncodedBase64")
.compact();
You can get any of claim value at time of parsing JWT token.
Step 3: Time to validate user and session
After getting User entity by JWT, check if lastSessionResetDate is valid till time, else unauthorized the request.
You must be already fetching user from database, so you don't need to make any other query because our new added column lastSessionResetDate will be a part of the same query result. (You just have to add one if-else block).
Note: You can also get prompt value in response from users for log-me-out-from-other-devices
Hope you have got an idea, Happy Coding!!
Assuming you use JWTs as Bearer tokens, depending on your requirements, it might not be sufficient enough "to keep track of the last token used by the user, then invalidate that once we detect a new log-in.".
As Bearer tokens (independent on whether it's JWT or not) are send by the client, the client is also aware of the token, which allows the sender to also copy and paste tokens from one device to another (or issue requests including the same token from various devices).
Your requirements sound like a standard use case for proper session management that allows e.g. for user-agent binding.

Is it secure to store a refresh token in the database? (To issue new access tokens for login purposes). Or is there a method to do it easier?

Currently im trying to gather knowledge on how to implement an auth system (a login) . And during my research i've tried to implement a JWT based solution in my backend.
I have an express server which allows me to register an user , storing its password (encrypted) and its email.
After that on login, it generates an access token (short lived, 5min) , in order to access protected routes, and a refresh token (long lived, 7 days), in order to generate new access tokens once the previous expire.
In my current implementation, im storing the refresh token in my database, so I can use it every time I want to generate a new access token.
But is that secure? As far as I understand, storing an access token in my database is dangerous, so its better to create a short lived cookie stored one. But... the refresh token? As far as i understand it would be dangerous since it basically allows to generate new access tokens, so I dont see the point of why not simply storing a long lived access token in my database, an generating a new one in every login.
Whats is the refresh token for then?
Since im following some tutorials in order to achieve this, this is how my refresh_token route looks
//get a new access token with a refresh token
app.post('/refresh_token', (req, res) => {
const token = req.cookies.refreshtoken
//if no token in request
if(!token) return res.send({accesstoken : ''});
//if we have a token we verify it
let payload = null;
try{
payload = verify(token, process.env.REFRESH_TOKEN_SECRET);
}catch(err){
return res.send({accesstoken: ''});
}
//if token is valid check if user exist
const user = fakeDB.find(user => user.id === payload.userId)
if(!user) return res.send({ accesstoken: ''});
//if user exists check if refreshtoken exist on user
//Is this really necessary? <-------------------------------------------
if(user.refreshtoken !== token){
return res.send({accesstoken: ''})
}
//if token exist create a new Refresh and Accestoken
const accesstoken = createAccessToken(user.id);
const refreshtoken = createRefreshToken(user.id);
user.refreshtoken = refreshtoken;
//send new refreshtoken and accesstoken
sendRefreshToken(res, refreshtoken);
return res.send({accesstoken});
})
The arrow comment is where I have my doubts, ok its returning an empty access token if my database table user (its a mock database so an array so far) , doesnt have stored a refresh token. But why would you do that? Is that used to not let arbitrary users generate access tokens? As far as I understand thats the only reason of why would I do that.
But again then, isnt it dangerous to store in a database? WHy not simply store the access token then make it a long lived token, and generate a new one in every login?
Is there a method to do it simplier than with jwt?
Why access tokens should be short-lived: if you want a decentralised auth flow (authentication service signs a token, other services can verify if it's valid using an asymmetric public key), you want that token to be short-lived because it cannot be blacklisted in case it's stolen (an attacker can use it until it expires). You can of course blacklist access tokens using i.e. Redis, but your auth flow won't be decentralised anymore. All services will have to validate that token using the asymmetric public key AND check if it's blacklisted or not (better just ask authentication service if it's valid or not).
This is how I would go about it:
5 minute access token as JWT (self-contained, don't need to store it
anywhere).
7 day refresh token for one-time usage: generate random secret (don't need to sign it/encrypt it), store it in Redis with a 7 day TTL (or MySQL with a valid_until timestamp). On /refresh_token validate the provided token (check if it's in Redis/MySQL) and delete it. Generate a new access and refresh token pair. (I like to rotate refresh tokens as well, it makes it a bit more secure: it's probably already rotated=invalid if stolen)
This way the auth flow stays decentralised and refresh tokens can be revoked if they are stolen.
Refresh tokens should be encrypted in storage. The OAuth 2.0 Threat Model and Security Considerations RFC goes into this:
4.5.2. Threat: Obtaining Refresh Token from Authorization Server Database
This threat is applicable if the authorization server stores refresh tokens as handles in a database. An attacker may obtain refresh tokens from the authorization server's database by gaining access to the database or launching a SQL injection attack.
Impact: Disclosure of all refresh tokens.
Countermeasures:
Enforce credential storage protection best practices
(Section 5.1.4.1).
Bind token to client id, if the attacker cannot obtain the
required id and secret (Section 5.1.5.8).
And then the referenced section 5.1.4.1.3:
5.1.4.1.3. No Cleartext Storage of Credentials
The authorization server should not store credentials in clear text. Typical approaches are to store hashes instead or to encrypt credentials. If the credential lacks a reasonable entropy level (because it is a user password), an additional salt will harden the storage to make offline dictionary attacks more difficult.
Note: Some authentication protocols require the authorization server to have access to the secret in the clear. Those protocols cannot be implemented if the server only has access to hashes. Credentials should be strongly encrypted in those cases.

Looking for some advice on front end/backend user authentication

I'm running a mock expressjs server in the back, and ember (ember-simple-auth) on the front with the ember-simple-auth-token addon. I'm using JWT tokens. I'm trying to decide whats the best way to send my user information. Usually when the user submits their credentials I create a new token, store a copy of it in the database (I'm using mongodb), send it to the frontend and then use the token to fetch information user information. I have a /auth/token (which authenticates and sends the token, makes a copy and stores it in the database) & /current_user route which gets called on the initial login, which uses the token and fetches the user info.
Is it better to simply send the user info in the initial payload of the token over having a separate route? Should I be storing a copy in the database in order to do a comparison and retrieve user information?
Also what are the advantages of a token refresh?
you are using Jwt-Auth for authentication.
-According to my knowledge after sending the user credentials u will respond with the token if credentials are correct otherwise send error.
-why are you saving the token in db ?.
you will send token to client (stateless). If client requests for data then we need to check for token. if it validates then return proper response otherwise return token error.
-why you need token refresh?
for security purpose. After response every time change the token.
TTL your token will be valid for some time (say 60 mins). after that it will be invalid.
This is how JWT works.

Identifying/Managing users based on session ID (Mapping between Session ID to User ID to User Data)

I wrote a web-app that authenticated a user via Facebook connect (o-Auth).
After the user have authenticated I have a facebook token.
Using this token I send a request to Facebook to grab its basic user information.
At this point I have the user unique Facebook id and I know who it is.
How should I link between the user, the token and it's data in the database?
Right now my schema is pretty simple: facebook_id is the Primary key, and there are some other columns that includes the token and the user's data. Is that the correct way to do it?
At which point do I need to set a unique SESSION_ID (cookie) on the user request? After it authenticated?
I am confused about this part (and with Session management in general). When i set an attribute on a session does the browser remember it an send it in every request to my server? across all pages?
And the most important question is, how do i map between the SESSION ID and the user? Once i set a session id on his request, i need to figure out on every request who it is. What's the best way to do it?
That is fine, all you really want to do is to be able to match to a particular Facebook User ID with the data created by or in your web app that doesn't come from the Graph API .
At the moment you complete the Login flow (when you receive the Access Token). When you set a session the browser will remember the key-value pair in it until the session is cleared. So you want your code to be able to associate someone using a browser with a particular user in your database (or not if they don't have a session). Thus, whatever session value you use, you need to also store this in the Database alongside the User ID.
See above.
Honestly though, the very easiest way of doing this is to just use the Facebook Javascript SDK. This will handle all the access token retrieval and user persistence through cookies automatically, without you having to write code for it. Ultimately this will mean that all you need to do is store the Facebook User ID in your database alongside the app-generated content and won't need to worry about storing access tokens or session variables. There's a simple step-by-step guide here:
https://developers.facebook.com/docs/howtos/login/getting-started/
(in Step 5 you'll receive the User ID and you can make an AJAX call to server-side code from here to store it in your database)