I recently found my self in need of developing a public API for my application. I developed my application with node.js and mongodb. After some research, I decided to use jwt for generating API keys for users and authentication. For authenticating jwt tokens, they come with the benefit of not needing to store them in a database, as they can be decoded and verified without knowing the exact generated token.
But I see that lots of applications show the users their API keys in the application dashboard, so I need to actually store the token in order to show them to the users later on. I know that storing tokens is a bad idea and in case of a database breach, it could let the hackers impersonate others with the API keys.
So long story short I am trying to find a way to not to store the exact tokens, but store only the payload in the database and every time users request their API keys I just generate the same one with a SECRET and pass it to them. I currently find that if on the signing token step, I pass the same payload with the same iat (issued at) every time, the generated token will be the same every time. So by saving the iat with the payload data in the database, I can generate the exact token every time.
Here is my mongoose schema:
var KeySchema = new mongoose.Schema({
name: { type: String, default: 'API Key' },
active: { type: Boolean, default: true },
iat: { type: Date, required: true },
project: { type: mongoose.Schema.Types.ObjectId, ref: 'Project', required: true },
});
And this is how I generate the same token with jsonwebtoken npm package:
import jwt from 'jsonwebtoken';
jwt.sign({ project, iat: CONST_TIMESTAMP }, config.secrets.session);
Now my questions are:
Does this approach is a good approach or is there a better way to achieve this?
Is there any good practice for generating API keys without storing them?
Does this even necessary (considering if there ever be any database beach, all of the data is already stolen)?
Is there any method other than using jwt to achieve this?
JWT is a standard approach for the given objective and for you I would recommend to use asymmetric signing key.
The advantage of this would be the consumers or clients will need to trust the issuer and there are multiple ways in which the public part of the key can be distributed.
So this does away with the requirement of key regeneration , also you can use the standard techniques for securing the private part of the signing key.
Related
I am doing a project with authentication using passport js with the Strategies (Google, Facebook, Local). I am working on the google part now but there is a small problem. When users register locally I save their data to the db (email,username,hashedPassword) but now when they register/signin through google I do not have access to their password (obviously). It is required in the user schema and I do not want to create a new user schema.
Could I save the password as the user id I received from google? I really want to know the correct path I should take.
You could add another field to your schema like provider that specifies the type of authentication that was used. Then you can use the mongoose required validator to make password only required if provider is 'local.' Something like this:
const userSchema= new Schema({
firstName: String,
lastName: String,
email: String,
username: String,
provider: {
type: String,
enum: ['local', 'google', 'facebook']
},
password: {
type: String,
required: function() {
return this.provider === 'local';
}
}
});
When you go use an auth provider, they usually will give you some sort of "token", maybe on your client or on a redirect on your server (depends on your implementation).
Then you have to validate that token with the provider (how to do this depends on the documentation of each provider), because you can not plain trust that a given token is valid.
Once you have validated with the provider that the token is valid, is common that with that token you have access to some info of the user, like email, name, phone number, etc (depends on how you configure your integration) and maybe store which provider they use (fb, google, apple, etc)
Then you can save this information in your schema, and then generate a session for this user (it may be a JWT or a session stored on the db)
Now, when a user sign in, you will do the same validation process of the given token with the user sent, then you can provide a new session
In summary, the key is that instead of using user/pwd to validate against your local database to authenticate a user, they will provide a token, and you have to validate that token against the provider they selected
I am implementing JWT in one of my node apps. I am wondering, if there is any definite format/ structure in which a refresh token should be generated?
By definite format I mean whether a refresh token contain any claims like a JWT?
UPDATE
Let's assume a refresh token to be: fdb8fdbecf1d03ce5e6125c067733c0d51de209c (taken from Auth0). Now, what am I supposed to understand from this?
Is this a random unique string?
Is this an encrypted string which has claims inside?
Short answer
A refresh-token is just a random string.
All the user-related information including claims go in access-tokens
Explanation
You should keep something like this:
{
_id: [refreshTokenId],
value: 'fdb8fdbecf1d03ce5e6125c067733c0d51de209c',
userId: [userId],
expires: [some date],
createdByIp: [some ip],
createdAt: [some date],
replacedBy: [anotherRefreshTokenId],
revokedByIp: [some other ip],
revokedBy: [some other date],
}
Refresh tokens are random strings generated by the authentication server. They are generated after successful authentication (for example, if the username and password of the user are valid).
Their sole purpose is to remove the need to exchange user credentials repeatedly. They are different from access-tokens.
An access-token usually has information about the user (like name, claims). These are usually short-lived. JWT is one example.
To get a JWT the app has to verify the credentials.
To add additional security, and to stop bothering the user for username and password every 15 mins, we just create a signature on the server-side and forward it to the app.
Next time, whenever the app needs to create a JWT, it can just send the signature back to the server. This signature is your refresh token.
Refresh tokens are also supposed to be saved somewhere.
So you would probably create a table/collection in your database, linking the refresh-token values with userIds and ip_address.
This is how you could create a session management panel for the user. The user can then view all the devices (ip_addresses) for which we have registered a refreshtoken.
I am making an application in which I am using JWT for maintaining sessions. When any new user registers then I provide a JWT token to user and store it in my database as well as in users browser. When user log out, then i delete that token from browser and my database.
But I want that if user is logged in from multiple devices then it it will log out from one device, it does not logout from other devices as well. How do I achieve this?
First, JWT are not supposed to be able to "log out", but to automatically expire, that is why you are supposed to set short expiresIn times.
It is because with JWT, sessions are handled by the client, it is not the server's responsibility to log out users, it is the user who just throw away the JWT.
In your case, I suppose you check if the JWT exist in your DB before allowing the user, and as such, you just need to search and delete the others JWT associated to that account.
But if you want to make things clean, embrace JWT logic: just set short life time, and wait for them to expire.
Just use an array of tokens in database. Each device will have it's own token in the array of tokens (each token was added to db when user first logged in from a new device) and when the user logs out from that device, only the associated token from the tokens array gets deleted. Here is an example of User schema:
var userSchema = new mongoose.Schema(
{
name: {
type: String,
require: true,
trim: true
},
.
.
.
tokens: [
{
token: {
type: String,
required: true
}
}
],
{
timestamps: true
}
);
Token-Based Authentication: JWT? Check. GET /items/:id... How?
This is a small question, but probably a big answer, as I'm a bit new to this...
Once I have provided a client a JWT -- and they wish to obtain a resource -- what does the logic-flow of verifying Client look like?
In other words, I have a JWT payload such as...
{
...
"sub": user.id
...
}
... and Client needs to access item 998 at /api/items/:id...
My current approach looks something like the following.
// ItemsController.lang | 'GET /api/items/:id'
var userId = jwt.decode(token).sub;
var isValid = checkUserIdInDatabase(userId);
var secureResource = ORM.findOne({ user: userId, id: request.itemId });
response.send(secureResource);
Along with this, when Client signs up/in, I provide them a response which looks like this...
{ user: { id: 998, email: 'no#username.com', preferences: [...] }, jwt: token }
Should I ever be sending id & email if I'm issuing a JWT?
Given that I should have middleware to check if my jwt.sub's [userId] value exists in the database, should I use this userId as part of my query, or should Client be sending Server the userId as request.body.userId since it obtains it upon sign in/up?
Is it a no-no to assign a userId to payload.sub?
Do I need to generate a new JWT with a new lifespan upon every request and send it to the client?
What are all my security blunders, what is best practice, and what would you do?
Every tutorial I look at shows a nice & clean high-level flow stating [simply] that 'if the JWT is verified, the resource is sent to the client'.
Can you please provide me with some direction on how all this token-based authentication stuff is supposed to work at the low-level -- namely, in order to request secure resources?
PreThanks,
Cody
This is solely my point of view and i am no expert, here it goes:
1) I think you should not send user ID in your response as there is not a lot of scenarios where i see that would be necessary for client to know its user id, you easily assign this to your token payload and use a middle ware like express-jwt
to do the decoding and giving the user id for you.
2) Do not rely on sensitive data sent by client, if you use express-jwt then it would assign user id with every request object eg: req.user.userId
3) you can assign user id and other small user session data in your payload, your client would require your secret key and to be able to decode that information , always have a strong secret key.
4) I think you should have token life span expire after a day or even less, there are scenarios where you may need to go longer i think 1 week should be maximum amount of time you should have your token life span. now you can always refresh your token and have your application check after some interval of time if its token is valid and request for refresh or new one after expiration .
5) may be these:
Have a strong secret key.
Use cookies to store secret key for web applications
Keep your token life span short as possible.
Use HTTPS protocol for APIs.
Write a middle-ware or use already existing middle-wares for user authentication. eg: express-jwt
jwt tutorial tutorial2 ,using storm-path and using passport
Hopefully this helps.
I am writing a nodejs application with passport.js-based authentication. It lets users message other users, where only authenticated users are allowed to retriever messages either sent by them or with them as the receiver. I am planning to implement multiple identity providers, such as facebook, google, and maybe local authentication as well.
The user schema i set up using mongoose looks like sort of like this:
var userSchema = new mongoose.Schema({
googleId: String,
facebookId: String,
email: { type: String, required: true },
}, {
strict: false
})
module.exports = mongoose.Model('User', userSchema)
Now the approach I had in mind was this:
A user is presented a sign in page
On this page they are presented a choice of identity providers
They get redirected to authorization page, granting access to the requested scopes, getting redirected to my specified callback URL
There is either already a user with the according ID or a new one is created.
Now when they try to receive the message, I want to authenticate them again in order to grant authorization to obtain the message. How they authenticate really does not matter, as long as it is any of the strategies I configured; there is however no such thing as app.get('/messages', passport.authenticate('any'), done), so how would I approach this?
One option is to pass the strategies you want as an array into passport.authenticate([Strategies]). The link below shows that nicely.
passport.js with multiple authentication providers?
Another example from the author of passport:
https://github.com/jaredhanson/passport-http/blob/master/examples/multi/app.js