-
Notifications
You must be signed in to change notification settings - Fork 179
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
Decode JWT token for validation #1480
Conversation
Tests are passing locally but require Werkzeug<2.3.0 Pending #1479 resolution to have CI test passing and dependencies updated |
Now thinking that I did not consider the case when the token expires: how will client request a new token and authenticate itself? |
Thanks a lot for working on this, it was badly needed. |
After analysis, I think token issuance responsibility should be moved to RPC clients. I did start working on an implementation in which a JWT (public) key parameter can be added to |
Maybe not a good idea... I will rather refine server side implementation and address access token refresh issue with refresh tokens as described here. |
From a brief read, it does look like an interesting idea, though rather complex. Are you thinking about something like this? https://stackoverflow.com/a/71900761 .. if so, how would it work client-side? I didn't have a clear idea about this expiration issue myself, when we first (half-) implemented this. |
Idea is as follows, for a typical flow:
|
Yes that's approximately what I understood, it is a bit complex, and I guess we don't get out of the box with the PyJWT package right? But from a logic perspective it seems entirely fine. |
Refresh logic requires a custom implementation indeed, PyJWT will solely help validating tokens |
I have just refined token validation implementation, it should now be straight forward to add a refresh token endpoint. |
Added |
Updated PR comment |
Looks like really good work, I haven't yet found any issues in testing it out. I'll keep looking at it for a while. You need to update https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/api/wallet-rpc.yaml with the new It really does seem a shame that we need such a large amount of code here to do stuff that really feels like it should be handled by an underlying library ... but it's certainly better to have sensible logic instead of basically broken (or, charitably "very incomplete") logic for tokens that we had before. |
OK should be ready after adding yaml and squashing. |
Updated OpenAPI doc and squashed |
The issue is with the authorization logic that cannot really be delegated to a library imho. Considering every application will have different authorization needs, importing a "flexible library" would add many more lines of code than implementing a custom logic, which we do here with less than hundred lines. |
Two things that are not hugely needed but worth considering:
|
jmclient/jmclient/auth.py
Outdated
arguments and wallet_name property. | ||
""" | ||
token_type = "refresh" if is_refresh else "access" | ||
claims = jwt.decode( |
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.
Afaict from looking at the pyjwt code, this will throw ExpiredSignatureError
if we passed the expiration. I think there's a value in catching that to bubble up an error message for the caller? So they can distinguish between a rejected token that is just invalid vs one that is expired?
Sometimes there's an argument in computer security for never telling the caller the reason for the failure, since it can allow probing attacks. That doesn't seem to apply 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.
Afaict from looking at the pyjwt code, this will throw
ExpiredSignatureError
if we passed the expiration. I think there's a value in catching that to bubble up an error message for the caller? So they can distinguish between a rejected token that is just invalid vs one that is expired?
ExnpiredSignatureError
can indeed be caught and bubble up to the caller in error description.
Sometimes there's an argument in computer security for never telling the caller the reason for the failure, since it can allow probing attacks. That doesn't seem to apply here.
That was my argument as a matter of facts, but thinking about it, expiry date is already readable from token. I will adjust code.
@AdamISZ you'll have my comments shortly. We may want to modify a few things. |
Reading OAuth 2.1 draft specs but I came to realize that my implementation differs from it on a few point.
Since changes are rather straight forward, I would like to align to standards before merging. |
Could you explain point 3? I don't understand what that means. |
An example will speak by itself:
|
|
I'll make it a priority to review it as I can over the next day or so. @theborakompanioni any feedback from you (or other JAM people) would be great. |
Have reviewed the update and I believe it all makes sense as far as I have checked. Also tested it out, though only using the test suite. I just have that one question above for now, though I will probably try to check a couple more things. |
OK so I tested this in as realistic a way as I could (using POSTMAN) to call In the I understand that the intention was to have it be dynamic, i.e. that the |
The JWT validation update in PR 1480 contained a small error: by calling the function get_POST_body() twice, the content of the request was unavailable in the second call. This calls only once, but has the downside of requiring a specific set of keys in the json request data. Hence this might be fixed to be more flexible later, see comments in the PR.
@theborakompanioni please see the follow up commit directly after ( a847df9 ), and let us know how this goes for JAM clients, as I can well imagine this is a non trivial extra bit of coding client side. |
Thanks for the fix. I will address the dynamic behavior of this method while implementing authentication/credentials (#3) which will most likely need to manage another grant type. |
Right, sounds good. I realized after pushing it that I was being a bit braindead, as well as any sophisticated way of doing it, there is a very simply way of doing it: deep copy the |
Will immediately start after the new Jam version for v0.9.10 is released. 🙏 |
I am currently implementing this for Jam. This is the result of a recovery test ( {
[...]
"scope": "test.jmdat walletrpc import.jmdat"
} {
[...]
"scope": "test.jmdat walletrpc import test.jmdat import.jmdat"
} Note: Existing wallets are |
Thanks for the info, I'll have a look at it shortly. |
fyi @roshii, I don't know if this is on purpose or a mistake. When the access token is not refreshed and a request is made with the expired token, the message in
Except for the report above (space in wallet name), it seems to work fine. 💪 |
Thanks for the info. Headers should contain the following info, with no hint on class name.
I'll take a look to have it done has it should.
Good to read. Thanks again. |
c88429d JWT authority fixes (roshii) Pull request description: - Fix JWT unit tests (`async` was somehow making tests always successful therefore hiding some issues) - Fix `WWW-Authenticate` header construction (#1480 (comment)) - Encode wallet names with base64 in scopes to allow for space delimited names (#1480 (comment), joinmarket-webui/jam#663 (comment)) - Fix syntax errors in OpenAPI RPC documentation (#1559) Top commit has no ACKs. Tree-SHA512: 6625c4c457c4caf3b4979505334c955bec50fcc0b01707e313dc772571c5c8c8b3ca359a18b5e67f1b0d0eb9b2b7c234ae9716d785234e8de0f3bfb76d53d29a
The JWT validation update in PR 1480 contained a small error: by calling the function get_POST_body() twice, the content of the request was unavailable in the second call. This calls only once, but has the downside of requiring a specific set of keys in the json request data. Hence this might be fixed to be more flexible later, see comments in the PR.
Implement
jmclient.auth
module to manage JWT.Upon successful authentication (e.g. unlock wallet), response includes both a
token
and arefresh_token
. The former can be used for call authentication, valid for 30 min. After expiration, user can call/token/refresh
endpoint with his expired access token in header and refresh token in POST call payload to get both a new access and refresh token. Refresh token is valid for 4 hours.Anytime a new access token is issued, refresh token signature key is re-initialized, invalidating any previously issued token.
Tokens are scoped to a specific
wallet_name
and a genericwalletrpc
category, and should allow future upgrades such as authorization granularity.Fixes #1297