Story #2359
closedAs a user, I can use JWT tokens for authenticaton
100%
Description
Use djangorestframework-jwt to implement JWT support.
One should be able to:
- acquire a JWT after some other initial authentication (e.g. basic auth)
- use JWT for further authentication
- invalidate JWT
- by request
- on user logout
- on user delete
- when password-like credentials used in the initial authentication were changed
- configure expiration time for the JWT token (the default value - 1 week? 3 weeks?)
More explanations about use of JWT in Pulp could be found in pulp-dev list
One of the possible solutions for invalidating such tokens as JWT would be checking some timestamps on each request.
Even though Pulp does not expect tons of request per second it still looks a bit expensive to me to call db on every request, iiuc. Just a thought to keep in mind.
Related issues
Updated by ttereshc about 8 years ago
- Blocked by Story #2358: As a user, I can authenticate with username and password stored in Pulp added
Updated by ttereshc about 8 years ago
- Related to Task #2090: Create a plan for user/auth in 3.0 added
Updated by ttereshc about 8 years ago
- Has duplicate Story #2367: As a user, I can configure the expiration period for JWT tokens added
Updated by mhrivnak about 8 years ago
- Groomed changed from No to Yes
- Sprint Candidate changed from No to Yes
Updated by fdobrovo over 7 years ago
After discussion with Brian it was suggested that it might be better and easier to adjust our requirements than to rewrite basicly whole rest_framework_jwt.
The MVP states about JWT requirements:¶
-
As an API user, I can have documentation to generate a JSON Web Token (JWT) without the server being online.(Not dependent on JWT implementation) - A user authenticated with HTTP/HTTPS "Basic" auth can acquire a non-expiring JWT to access the API.
- The JWT shall have a created timestamp which can be used to invalidate
- The JWT shall have a user identifier (its primary key)
- As an API user, I can authenticate any API call with a JWT.
- As an API user, I can invalidate all JWT tokens for a given user issued earlier than now.
- As an authenticated user, when deleting a user 'foo', all of user 'foo's JWTs are invalidated.
- The rest_framework_jwt out of box support numbers 5, 7. and to some point number 4, but using pk is deprecated and they move on usernames instead.
- To support number 2 own view have to be written. (Their "login" view handles auth itself via POST.)
- Number 4 The payload of JWT token is public. Therefore anyone in case of using usernames as rest_framework_jwt does can read in some cases email address or kerberos id of user whose token it is. Not sure if it's issue or not.
Number 3 and 6:
- The plugin does not use "iat" field. Instead it uses "exp" field and all tokens are for limited time. The expiration can be turned off by one boolean.
- To support the ability to invalidate tokens of one user rest_framework_jwt provides JWT_GET_USER_SECRET_KEY Changing such secret_key on user module would lead to invalidation of all tokens issued before for such user. This have one essential benefit over storing timestamp of creation. for point 1. Because in order to be able to generate the JWT offline one have to know the secret key. If there would be one pulp-wide secret key everybody who knows it can make token for any user without any additional informations needed. So it's a security issue I think.
Using the way as it is now specified in MVP leads to almost completely rewriting the rest_framework_jwt. So I think we should discuss changing our requirements if it wouldn't be better to alter them a little bit.
Not finished Work in progress of implementaion by MVP here: https://github.com/BrnoPCmaniak/pulp/tree/JWT_token
Just one more idea I was thinking that the secret key in per user mode could me some sha of salted password like "pulp3_jwt_auth:<password>:pulp3_jwt_auth" so it could be generated anywhere without need to know anything more than just username + password. So it would be truly offline generation. Maybe even add few letters of some per pulp db install secret code to add another layer of security to password if the token would be compromited?
Updated by bmbouter over 7 years ago
Thank you for such a detailed gap analysis. Here are some thoughts and questions:
+1 to adjusting use case 4 to use the username instead of the the pk. The username is unique so that should work perfectly.
When you say "payload of JWT token is public" what does that mean? I expect the client server connection is encrypted with SSL and that one user cannot request the JWT of another user with first providing basic auth creds.
For use case (2) are you saying that the user's credentials to get the JWT token via the normal way this library does it is to put the creds in the POST payload? Could you show an example of that payload to make it concrete for us? Maybe we should switch to that.
+1 to adjusting use case (3) to use the "exp" instead of the "iat"
+1 to using the JWT_GET_USER_SECRET_KEY to accomplish use case (6). For that use case I think we just use that feature and use case (6) is resolved, yes?
Updated by fdobrovo over 7 years ago
bmbouter wrote:
When you say "payload of JWT token is public" what does that mean? I expect the client server connection is encrypted with SSL and that one user cannot request the JWT of another user with first providing basic auth creds.
What I meant was that you don't need to know the secret key to decipher the token's payload. So if you get hands on token from config in fs or something you can tell who it belongs. But as I'm thinking now it's not something one would worry about.
For use case (2) are you saying that the user's credentials to get the JWT token via the normal way this library does it is to put the creds in the POST payload? Could you show an example of that payload to make it concrete for us? Maybe we should switch to that.
The jwt serializer is here
It uses the USERNAME_FIELD on the user model as the name of the username field. So the payload would look like this:
The payload can be in these formats and any other supported by rest_framework for requests (more on it here):
$ curl -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"admin"}' https://localhost:8000/api/v3/token
or
$ curl -X POST -d "username=admin&password=admin" https://localhost:8000/api/v3/token
It has one rest_framework obstacle, to obtain the token the view would have to have set authentication classes to empty. Thanks to that we couldn't easily have under one endpoint more than just obtaining the token. (I think it might be done by dispatching anything else to proper view which would have the classes set, but I'm not sure when does rest_framework check for it.)
+1 to adjusting use case (3) to use the "exp" instead of the "iat"
+1 to using the JWT_GET_USER_SECRET_KEY to accomplish use case (6). For that use case I think we just use that feature and use case (6) is resolved, yes?
Yes it would solve the use case 6, but with that the use case 3 becomes redundant. We can set the expire time to 0. And thanks to that the token would have timestamp of creation on it and also thanks to presence of "exp" the tokens for one user wouldn't be all the same.
Updated by bmbouter over 7 years ago
fdobrovo: I rewrote some of the use cases based on our discussion. I put my rewritten work here: http://pad-katello.rhcloud.com/p/pulp_JWT_use_cases
I think these changes will allow the JWT plugin to be used more easily. What do you think? I think once we agree on the adjustments to the use cases we can go to the mailing list or an MVP call to check in with others.
My one area of confusion is that one use case claims that it gets a non-expiring JWT and there is another use case that says we would have an 'exp' field. Is 'exp' optional? I put the word optional on there, but I don't know if it really does that or not. If it has to have an expiration then we probably can't also have non expiring tokens.
Updated by fdobrovo over 7 years ago
The plugin adds the field "exp" no matter what. The time exp is actual time plus time from setting. Optionally we can check if the exp time is bigger than actual time or not. We could set the time difference to 0 so the "exp" would virtually by value become "iat". Other than that I think I agree.
But we should also discuss the way of obtaining the token. If it will be by base auth or any other rest framework activated auth or that user have to supply credentials in POST.
Updated by bmbouter over 7 years ago
- Sprint/Milestone changed from 39 to 38
fdobrovo wrote:
The plugin adds the field "exp" no matter what. The time exp is actual time plus time from setting. Optionally we can check if the exp time is bigger than actual time or not. We could set the time difference to 0 so the "exp" would virtually by value become "iat". Other than that I think I agree.
For ^ I think we should adjust the use case to have the JWT expire after a user specified amount of hours. That would be a systemwide setting. I've revised line 6 here: http://pad-katello.rhcloud.com/p/pulp_JWT_use_cases
But we should also discuss the way of obtaining the token. If it will be by base auth or any other rest framework activated auth or that user have to supply credentials in POST.
I think the alternate use case that aligns most heavily with that plugin is to have have an unauthenticated user supply the credentials in the POST body. Is that right? If so this could be highlighted and we can check in with others if replacing the JWT use cases with these alternates is a good idea.
Does the drf-jwt library easily support non expiring JWT? I think maybe it doesn't. If it doesn't then we should remove line 5 here: http://pad-katello.rhcloud.com/p/pulp_JWT_use_cases Related to that, I think lines 5 and 11 are redundant in the etherpad doc. What do you think?
Updated by fdobrovo over 7 years ago
bmbouter wrote:
fdobrovo wrote:
The plugin adds the field "exp" no matter what. The time exp is actual time plus time from setting. Optionally we can check if the exp time is bigger than actual time or not. We could set the time difference to 0 so the "exp" would virtually by value become "iat". Other than that I think I agree.
For ^ I think we should adjust the use case to have the JWT expire after a user specified amount of hours. That would be a systemwide setting. I've revised line 6 here: http://pad-katello.rhcloud.com/p/pulp_JWT_use_cases
That's definitely an option that is easily achievable.
But we should also discuss the way of obtaining the token. If it will be by base auth or any other rest framework activated auth or that user have to supply credentials in POST.
I think the alternate use case that aligns most heavily with that plugin is to have have an unauthenticated user supply the credentials in the POST body. Is that right? If so this could be highlighted and we can check in with others if replacing the JWT use cases with these alternates is a good idea.
Yes it is. But it brings additional struggle. I expect that it may be difficult to achieve basic auth and POST auth under one endpoint, if we would like to support both.
Does the drf-jwt library easily support non expiring JWT? I think maybe it doesn't. If it doesn't then we should remove line 5 here: http://pad-katello.rhcloud.com/p/pulp_JWT_use_cases Related to that, I think lines 5 and 11 are redundant in the etherpad doc. What do you think?
The JWT_VERIFY_EXPIRATION option in Additional Options provides ability to turn off checking if the token is expired or not.
Updated by bmbouter over 7 years ago
fdobrovo wrote:
Yes it is. But it brings additional struggle. I expect that it may be difficult to achieve basic auth and POST auth under one endpoint, if we would like to support both.
I want to get more input from others, but I think for that one endpoint we could only support POST auth for the purposes of getting a JWT token. I think adjusting the use cases to match would be good. That would let us use the dependencies default behavior without modification.
The JWT_VERIFY_EXPIRATION option in Additional Options provides ability to turn off checking if the token is expired or not.
I updated the revised use cases once again in three ways:
- allow the configuration to disable JWT expiration
- allow the configuration to specify the expiration time
- A few small clarifications that the URL to acquire a JWT only accepts POST auth.
I'm going to email pulp-dev to hopefully bring up these use cases on the list and switch to them on this Tuesday's MVP use cases call. Sound OK?
Updated by fdobrovo over 7 years ago
- Status changed from ASSIGNED to POST
Updated by jortel@redhat.com over 7 years ago
- Sprint/Milestone changed from 43 to 44
Added by fdobrovo over 7 years ago
Added by bmbouter about 7 years ago
Revision 7bb1b3c4 | View on GitHub
Add JWT dependency to docs builders
Pulp3 will depend on a DRF plugin called djangorestframework-jwt. When building the docs it needs to be able to import this dependency. This will update both the PR and regular docs builders.
Added by bmbouter about 7 years ago
Revision 64ce18a1 | View on GitHub
Add pulp dependencies to docs builders
When building the docs we need a full pulp3 environment for the docs to build correctly. This is due to the importing that occurs during docs building.
Updated by fdobrovo about 7 years ago
- Status changed from POST to MODIFIED
- % Done changed from 0 to 100
Applied in changeset pulp|a7fe25d9be48945e630abe66a45ce21126f8e910.
Updated by bmbouter about 5 years ago
- Status changed from MODIFIED to CLOSED - CURRENTRELEASE
Add JWT token auth
closes #2359 https://pulp.plan.io/issues/2359