sanic-jwt
sanic-jwt copied to clipboard
Logout
I'm aware of the debate on if and how to implement 'logout' functionality for JWT. Two enhancements that come into mind are adding 'delete_refresh_token' to the framework, and creating a skeleton for blacklisting. If there's a decision on an optimal way forward, I could implement and create a pull request if it helps.
While I am not off the bat opposed to the idea, I think it needs to be thought thru some. Inherently because JWTs are stateless, there is nothing to logout from.
I do see the usage in perhaps having an endpoint that would delete the refresh token, but I see this more as a feature that the developer would want to implement as a custom endpoint. Afterall, "logout" could mean a lot of different things for different applications. Your proposed use case probably does warrant an example though on how that would look and be setup. Something like this:
from sanic_jwt import BaseEndpoint
from sanic import responses
class Logout(BaseEndpoint):
async def post(self, request, *args, **kwargs):
user_id = request.app.auth.extract_user_id(request)
key = f'refresh_token_{user_id}'
await aredis.delete(key)
response = responses.json({"OK": True})
return response
Initialize(
...
class_views=(("/logout" Logout),),
)
Now... tell me more about the idea you have for blacklisting? Configurable to blacklist on different criteria? Perhaps IP, user_id, etc? Maybe blacklisted for a time period only? What about white listing? An interesting idea. One that might best be served as an example as well and not a core feature.
Thank you, while JWT is server-less, the server is still responsible to issue & sign the token. I would assume that in most cases, the token will be used as authorization to certain services, and not just authentication (there is a bigger discussion on aaa & micro-services but that is off-topic).
The “issuing״ authority has certain responsibility on the life-span including revocation of the token and/or the refresh-token, regardless of the server-less approach.
I would think there are two main use-cases: logout (let's not forget cookies) and an abrupt change in authorization - such as depromotion of user privileges.
In the logout case, I think the framework should provide the option to delete the refresh token, at least as a “hint” or even promote as best-practices - not just as an example.
The blacklist is intended to close the authorization gap between refreshes. I understand there could be way around it using the x5c header but that seems like an overly dramatic solution.
I'm not sure you want to go down the path of IPs and whitelist, my reasoning is simple: nothing lives in a vacuum, including RESTful APIs, and it's better to leave IP and equivalents to other components in the architecture, such as the reverse-proxy.
My suggestion is: i. Include delete-refresh-token as part of if the initialization and core end-points (short term) ii. Augment current design with blacklisting (mid-term)
Sorry for the long response. Really like what you’re doing here!
On Mar 27, 2019, at 1:05 PM, Adam Hopkins [email protected] wrote:
While I am not off the bat opposed to the idea, I think it needs to be thought thru some. Inherently because JWTs are stateless, there is nothing to logout from.
I do see the usage in perhaps having an endpoint that would delete the refresh token, but I see this more as a feature that the developer would want to implement as a custom endpoint. Afterall, "logout" could mean a lot of different things for different applications. Your proposed use case probably does warrant an example though on how that would look and be setup. Something like this:
from sanic_jwt import BaseEndpoint from sanic import responses
class Register(BaseEndpoint):
async def post(self, request, *args, **kwargs): user_id = request.app.auth.extract_user_id(request) key = f'refresh_token_{user_id}' await aredis.delete(key) response = responses.json({"OK": True})
return response Initialize( ... class_views=(("/logout" Logout),), ) Now... tell me more about the idea you have for blacklisting? Configurable to blacklist on different criteria? Perhaps IP, user_id, etc? Maybe blacklisted for a time period only? What about white listing? An interesting idea. One that might best be served as an example as well and not a core feature.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.
So, I have been thinking more and more about this. While I certainly do not think this idea would be a good thing to enable by default, I also do not see the harm in adding it since it potentially can make things easier for developers (instead of coming up with their own solutions to this).
I can think of five different ways that this could be achieved.
- The simplest solution would be something like what @amor71 suggested: provide an endpoint to delete
refresh_token
. While this does not per se disable usage of theaccess_token
it does take a step in the direction. It probably requires the most work for the developer to complete the cycle to achieve a true "logout". - Same as the first, but with some sort of a flag (ex
Initialize(enable_logout=True,...)
) that then makes the system require therefresh_token
to be present on each request. If it cannot be validated (because it has been deleted), then no access. This sort of perverts the usage of arefresh_token
, but could work. - One thing that could be done now, is create some sort of a
CustomClaim
that embeds a session id into the payload, and then verifies it on each request. One problem with this is thatCustomClaim
does not currently supportasync
(doing so requires a lot of change to the API). If this were to be an adopted practice built into Sanic JWT, that would be a must. - Another solution would be to add
access_token
to a temp in memory store for duration of expiration. Again, this would require something like:Initialize(enable_logout=True,...)
. After "logging out" (which means adding the token to a blacklist), it is checked on each subsequent request. To prevent a memory leak, we would need to implement some sort of periodic process to clean the store of expired tokens (which wouldn't work anyway). - The last idea would be to operate this thru scopes. The "logout" endpoint would need to set a flag (probably with a handler, potentially with an in memory store) that disable access by way of scope. This one would be best served with the addition of
sscopes
to replace the scopes module in Sanic JWT (which I am planning to do at some point) since it has scope negation in it. See #103
Thoughts?