-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use JWT to authenticate (workaround inside, but looking for a proper solution) #6390
Comments
The best way would be writing a new auth adapter like these. It would be nice to have it here. Would you be willed to tackle this and send us a PR? |
@davimacedo I could be wrong but I think the auth adapter is exactly the opposite. Using one of those adapter will get Parse server to log user in and issue a sessionToken to the client. Later requests will put that sessionToken in the header, and then it's business as usual. But here we don't want to use the sessionToken at all, we want Parse to use the JWT issued by Auth0. And frankly, it is probably faster and more stable since that does not require a lookup from database or cache |
Sorry. I've misunderstood your usage case. For your case case I think the middleware before Parse replacing jwt to session token is the way to go. You are not planning to use the SDKs, right? |
With JWT use, you still need check token in fast database like redis to check forced token ban |
@Dynan7 We do not have any kind of that check right now. We rely on the token to be very short-lived and constantly refreshed. We probably will eventually do that so users do not have to login too frequently. Do you think my way of having Parse handle JWT is OK? Was your point that even now I have to do a check from database or cache, it is not worse than JWT since if JWT was setup properly this check is needed anyway? @davimacedo Is there a plan for Parse to replace session token with JWT? Will this be a small/medium/large project? |
@Dynan7 On second thought, we are using 3rd party OAuth 2 service (Auth0). Users get a refresh token in their browser and the actual access token expires after a very short time (could be as low as 60s or so). The browser will receive 401 if the access token expired and then browser will call Auth0 with the refresh token to get a new access token. When we need to ban someone, we just invalidate the refresh token, and soon their access token expires and they lose access. In this flow, no database or cache check is needed at all especially on our server side. Auth0 need to valid refresh token, but that's their job when generating access token. I think a proper solution for Parse to use JWT should NOT be what I did, but trust the JWT and have the permission attached to the request to access data with ACL working. Could be complicated. |
@sunshineo I am not aware of any plans to replace current session token to jwt but I believe if it is something that the developers can opt-in or not it would be welcome. I'd need to learn more about jwt but I guess it would be medium project (I'm considering we can user some hack in order to not change all SDKs but definitively I'd need to learn more about jwt). @acinader @dplewis thoughts? |
Thank you @davimacedo! Does anything come to your mind besides attach x-parse-session-token to request.headers ? Is it possible to create something else and attach it to the request that can achieve the same effect? |
That sounds good, if you update token so often, you really no need check it in the any storage. |
@Dynan7 Thank you for sharing! How do you use the permissions from JWT in Parse? How does that work with Parse ACL? |
@sunshineo you can look at this file to see how the session token is handled. Observe that you can also send it in the body on a post request instead of in the header. |
Thank you @davimacedo . It seems that I can create an Parse.Auth object myself from that user. I do not need to log the user in then get the session. I do not need to have never expired sessions in my database. This also saves a couple database calls. I'll try it and report back. |
After reading the code my conclusion is that the middleware will overwrite req.auth based on the fact that there is no sessionToken. So I believe my workaround is the only workaround without modify the Parse code. The good news is I'm feeling this is not going to be that hard. I'll try to do a fork and try something. |
I was able to add this simple change that allows me to pass down the Parse user I mapped from JWT on the request to the Parse middleware. sunshineo@2dcafa3 if (req.userFromJWT) {
req.auth = new auth.Auth({
config: req.config,
installationId: info.installationId,
isMaster: false,
user: req.userFromJWT,
});
next();
return;
} This saves one or two database call, and we are no longer messing with the Parse sessions. No more insane long expiration session. This workaround still requires user to wrap Parse server in express and map the JWT to a Parse server themselves before pass it down to Parse middleware. But is this the final stop? Should Parse take over the JWT decoding and user mapping? I can see that introduce a ton of configurations to support all the different decoding usecases? And I'm not even sure if the user mapping is possible without code. I'd like to hear your thoughts. Here is what our code looks like now with the hack above const express = require('express');
const app = express();
const ParseServer = require('parse-server').ParseServer;
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const jwtMiddleware = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'http://your-app.auth0.com/.well-known/jwks.json'
}),
// Validate the audience and the issuer.
audience: 'https://api.your-domain.com/v1',
issuer: 'https://your-app.auth0.com/',
algorithms: ['RS256'],
credentialsRequired: false,
})
app.use('/parse', jwtMiddleware)
const rejectInvalidToken = (err, req, res, next) => {
// this means no token was provided, using other auth methods
if (err.code === 'credentials_required') {
return next()
}
res.statusCode = 401;
res.send(err);
}
app.use('/parse', rejectInvalidToken)
const getUser = async (jwtUser) => {
const username = jwtUser.sub
const userQuery = new Parse.Query('_User')
userQuery.equalTo('username', username)
let users
try {
users = await userQuery.find({ useMasterKey: true })
}
catch(e) {
console.log('Exception when search for user: ', e)
return
}
if (!users || users.length === 0) {
return
}
return users[0]
}
const createUser = async (jwtUser) => {
const username = jwtUser.sub
const user = new Parse.User()
user.set('username', username)
user.set('password', username)
try {
await user.save(null, { useMasterKey: true })
}
catch(e) {
console.log('Exception when create user: ', e)
return
}
return user
}
const mapJWT2ParseUserHelper = async (req) => {
const jwtUser = req.user
if (!jwtUser) {
return
}
req.userFromJWT = await getUser(jwtUser) || await createUser(jwtUser)
}
const mapJWT2ParseUser = async (req, res, next) => {
await mapJWT2ParseUserHelper(req)
next()
}
app.use('/parse', mapJWT2ParseUser)
const parseApi = new ParseServer({
...your configs
});
app.use('/parse', parseApi);
app.listen(port, () => console.log(`Listening on port ${port}`)); |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
@sunshineo - thanks for this. Hugely helpful to be able to leverage JWTs in our deployment of parse (eg: offline authentication, abstracting authentication from authorisation). I'm running into a couple of problems. How did you solve the following issues:
|
Not sure about the /me endpoint but my change is merged in so if you provide JWT on the request to /me endpoint it should work If you have a JWT, you can try make a dummy request to your backend and verify if the JWT works. If it works, your frontend can treat the user as "logged in", if not, say the JWT expired, you should discard the JWT and frontend shows as the user is not logged in. |
This seems to have been stale-closed. We have since disabled the stale bot from this repo to handle issues manually. I'm therefore reopening.l since there seems to be still discussion going on. The issue can be closed onces it's clear that is can be closed manually. |
Yes, I'm assigning the parse user that I instantiate from my JWT to I also discovered properties in the Parse JS SDK that allow my JWT to be passed in the header as a bearer token:
This works well for me as I'm trying to run parse behind behind AWS API Gateway (with a lambda authorizer) using tokens generated from Cognito. I do need to fork this project in order to add support to call the login endpoint with a JWT instead of a password and store the JWT as the session token. This will make the change easy for my JS, Android and iOS clients while being sure to handle any potential lingering uses of the |
Anybody tried to store user roles on this JWT too? |
Did you guys test this method of authentication with ACL / CLP limited classes and object operations? Are they working with this? |
Have not tested, but the solution is setting the user on the request before doing anything. So if the user can operation on ACL/CLP limited classes and object, then the JWT should be able to as well. |
Thanks, I'll give it a try and add roles to it, and will inform in here. |
Are you using this approach together with Parse Dashboard by any chance? While I can successfully protect the |
@stephannielsen Your use case sounds very nature at first. But according to my memory, Dashboard is doing bunch of fancy stuff using the master key and you can see the master key in the browser's developer panel. So able to log into the dashboard means user is almighty. Using JWT on Dashboard is not possible |
Yep sure, I didn't plan adding jwt authentication to the dashboard. The thing is, with the solution above, Dashboard can't load any data as its requests are blocked due to a missing jwt token. However, my idea is now to add another Middleware which accepts requests with the master key as the Dashboard includes it in its request headers. |
I misunderstood your question before. Dashboard requests to the server is very strange, it does not have JWT of course, it also does not have master key in the header. The master key is placed in the POST body. Dashboard always to HTTP POST even when it need to do GET/PUT/DELETE, etc.
|
Whoops, looked at my dashboard request the wrong way. You're right, it is in the body of the POST. Now I also understand this better. If a token is present, you validate it. If there is no token, you rely on Parse built-in mechanisms to protect the resources. Makes sense, as this allows normal Parse behavior if you have public resources. In my use case all data/queries requires authentication so I simply returned 401 also if there is no token present. But I guess the more appropriate way is to use Parse CLP/ACL mechanisms in combination with access token validation just to replace Parse's session token mechanism. |
So this works fine with CLP and ACLs on normal Parse endpoints. But we also use cloud functions. Using the JWT for authentication to access the cloud function works fine, however it seems not possible to run queries in the same user context afterwards and leverage the JWT for CLP/ACL on queries performed in the cloud function. Typically, you can parse a session token to queries but now we don't have a session token for this user.
Or am I missing something? |
In my original post I said |
Yes, sure, but that requires to use sessions again which were no longer needed without cloud functions... Thanks for the quick reply. |
We need to support requests without Parse session token but a JWT from Auth0. We have a hack but wonder if there is a better way to do this. We don't like the part that we have to call db twice to find the user and then the session. We would have called the db even more times if the user or session does not exist. We had to give the session an insane long expiration time, but I hope that is not a problem. Lastly, we are not sure if setting the x-parse-session-token header is the right way to become that user on the server-side.
Here we share our hack
The text was updated successfully, but these errors were encountered: