-
Notifications
You must be signed in to change notification settings - Fork 41
Add endpoint for JWT refresh tokens #3270
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
base: master
Are you sure you want to change the base?
Conversation
🦙 MegaLinter status: ✅ SUCCESS
See detailed report in MegaLinter reports |
Test results 12 files 12 suites 11m 59s ⏱️ Results for commit 1b61d8b. ♻️ This comment has been updated with latest results. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## jwt-refresh-token-model #3270 +/- ##
===========================================================
- Coverage 60.73% 60.72% -0.01%
===========================================================
Files 606 606
Lines 43769 43800 +31
Branches 48 48
===========================================================
+ Hits 26581 26597 +16
- Misses 17176 17191 +15
Partials 12 12 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
257a05c
to
b384528
Compare
f3f4c12
to
d16dda5
Compare
b384528
to
c3c9e16
Compare
d16dda5
to
c2568eb
Compare
c3c9e16
to
ec2ead6
Compare
1417b06
to
f118619
Compare
66952f5
to
2190bad
Compare
f118619
to
49c7894
Compare
0f0ed15
to
825d9f3
Compare
49c7894
to
c87e7a1
Compare
825d9f3
to
c35d5b6
Compare
c87e7a1
to
50e46b4
Compare
2889fe6
to
315df0c
Compare
50e46b4
to
15b2279
Compare
14e45e7
to
4805ebf
Compare
4805ebf
to
63b9bc5
Compare
315df0c
to
d1bfbc3
Compare
a29801e
to
8403bc7
Compare
83c5074
to
511b5e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only nitpicks
|
a3db8c2
to
5f15bb6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got questions (that aren't necessarily showstoppers at all, just me being forgetful), but my major gripe is missing input validation.
Otherwise, this seems good :)
|
||
def post(self, request): | ||
incoming_token = request.data.get('refresh_token') | ||
token_hash = hash_token(incoming_token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I browsed http://127.0.0.1:8000/api/refresh/ and posted an empty request, and this line crashed terribly with an AttributeError
('NoneType' object has no attribute 'encode').
Some input validation would be proper here - bad input should result in possibly a 400 Bad Request (depending on how other parts of the API behave with bad input).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added validation that input was given and that its a string. That should be enough, I dont think its necessary to check that the string has the format of a real token - if its not valid then there will be no match for the hash in the db
@@ -73,4 +73,5 @@ | |||
name="prefix-usage-detail", | |||
), | |||
re_path(r'^', include(router.urls)), | |||
re_path(r'^refresh/$', views.JWTRefreshViewSet.as_view(), name='jwt-refresh'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to satisfy my own curiosity: I cannot recall why some API views are added to the DRF router instance, while some URLs are added manually to the URL config.
The noticeable difference (for me) is that URLs that are added manually like this do not show up in the official list of API endpoints (swagger/OpenAPI) when just visiting /api/
.
These URLs will also not be part of the versioned API. We might need a discussion on why we have this setup and whether we should keep doing it (@hmpf )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at this point I dont remember either 😛
probably had to do with it not being a modelview or something? similar to issues I had with #3354 perhaps
try: | ||
# If hash exists in the database, then we know it is a real token | ||
db_token = JWTRefreshToken.objects.get(hash=token_hash) | ||
except JWTRefreshToken.DoesNotExist: | ||
return Response("Invalid token", status=status.HTTP_403_FORBIDDEN) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I'm sort of thinking that as long as we can verify that this token was signed by our key, it should be treated as valid (until its claims or dates are determined to be invalid, that is).
At the moment, I've entirely forgotten why we need to keep a reference to issued refresh tokens in the database, I just remember pointing out that storing the hash should be enough.
Only thing I can think of at the moment is that we need it to keep an audit trail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that we need some way to revoke tokens that may have fallen in the wrong hands. If we accept all tokens that are signed by us and an "attacker" gets their hands on a refresh token, then they can keep generating tokens forever. The only way to stop them would be to change our keys so the signature doesn't validate anymore. Storing the hash is a type of whitelisting that can easily be revoked by deleting the hash in the db (which can be done via the planned frontend)
A side effect of storing the hash is that we don't need to validate the signature in this endpoint, checking the hash is enough
doc/reference/jwt.rst
Outdated
ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key | ||
# Don't add passphrase | ||
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come openssl cannot also be use to generate the key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be possible, this is how I've been doing it and it has worked well. I'll see if I can find the openssl equivalent just to have fewer dependencies here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
openssl genrsa -out jwtRS256.key 4096
seems to be the equivalent
unittests and integration JWT tests need keys for validating and generating tokens
91f2633
to
a869e1d
Compare
shit got all messed up after merging #3268 so I needed to force push to remove unwanted commits |
Adds some basic validation of the incoming token. No need to really check the format or anything, if its not a valid token in any way it will fail when we check the hash against the database. The important part is that the incoming token is a string that can actually be hashed
|
Based on #3268
Adds API endpoint for JWT refresh token. It accepts valid refresh tokens and returns an access token and a new refresh token while invalidating the old one.
Adds docs for local tokens, with caveat that its not fully implemented yet