pallets-eco / flask-security

Quick and simple security for Flask applications
MIT License
635 stars 155 forks source link

Question: adding accesstokens #783

Closed N247S closed 1 year ago

N247S commented 1 year ago

Goodday everyone,

I tried to add accesstokens which can be managed by the user to have a specific/limited set of permissions assigned to them (basically POLP). My current setup is to have a separate database-table for the accesstokens which have a reference to the original user. I extend the default UserMixin model so a accesstoken can be used as a user in code. A custom identity_loaded singal can provide the correct permissions this way.

The problem is the authentication of a accesstoken is obviously different compared to a real user. Intergrating this feel like I am monkey-patching a lot of the default authn-mechanism which I want to keep in tact for real users. There is no way to configure it per user-type/user-model, which means I have to make several work-arounds.

Basically I would prefer it would only use the session-less token authn-mechanism, but since that mechanism is hardcoded to compare the fs_uniquifier/fs_token_uniquifier property and is session/time based, it is not really usable for the accesstoken mechanism without modification.

Option 1

One way of achieving something closely to this is by overriding the request_loader from the LoginManager. That also mean the freshness timers are updated when using auth_required unless I override the session_interface. Besides it means I have to disable the csrf mechanism by flagging fs_ignore_csrf. In short, it is doable but feels like it is prone to messing up intended security-mechanisms unintentionally.

Option 2

A different way is patching the auth_required (and alike) decorators. Since a custom authn-mechanism cannot be used as the 3 supported mechanisms are hardcoded I have to create my own version. One which would leave the default machanisms intact, but also offer authentication specifically for the accesstokens. That would mean it is less dynamic (as one would have to specifically use my version of authn_required to support the accesstokens.

AcessToken specs


The question; what would be the better approuch, or is there something I have missed? I feel like the second option would be the better option if 'custom authn-mechanisms' are possible (not sure if there is a PR for it). Would love to hear any input on this.

jwag956 commented 1 year ago

Interesting. So not totally sure I understand all the constraints you have - however I have been working on a feature that will provide an API for applications to modify a requests/users permissions based on arbitrary metadata (the example I have implemented adds a permission 'fs_tf_setup' if the user has correctly set up two-factor auth.

Now - it is always worth separating authentication from authorization. Sounds like you are building a web app and are fine with using sessions as the method to authenticate. What isn't clear in your writeup is how and who decides which permissions to give a given user - you say 'managed by the user' - so do they log in (with all permissions the admin has granted them) and then request an accesstoken with fewer permissions? Are you trying to emulate oauth scopes?

So I think you want to be focusing on the authorization side (Permissions_required, roles_required). You could create a accesstoken and store that in the session and use that to populate the users permissions. You could add: identity_loaded.connect_via(app)(_on_identity_loaded) and in your _on_identity_loaded use the token to populate permissions - then the normal Flask-Security decorators could still be used. Look in Flask-Security-Too::core.py at what we do on identity load (not sure the order flask will call these in). The feature I am working on basically adds a formal way to callout from _on_identity_loaded to application code..

Sorry - a bit rambling ...

N247S commented 1 year ago

Thanks for your response.

To clarify a bit, I have 2 separate portions of the web-app.

  1. A user interactable app where amongst other things, tokens and permissions can be managed through a UI
  2. A session-less json-API for third-party / machine access.

The latter I want to be as restricted as possible, and is where I would like to use the accesstoken mechanism for. Not sure if that fits the oauth-scope idea? I thought that was mostly part of unified-signin?

Anyways, the identity_loaded portion is trivial for authorization I agree. But it is the authentication I want to keep separated in both mentioned portions. Especially because the second portion is 'session-less' I don't see how using a token for identity-population would be enough? Maybe I am overthinking this a bit

Not sure if this information makes more sence? Otherwise feel free to ask.

Ps. don't be sorry for 'rambling'. Any input is apreciated!

jwag956 commented 1 year ago

Ok - here is a thought - of course FS supports auth_tokens as a std authentication mechanism. So if I understand - if a user authenticates with an authtoken they should get different (less) permissions than if they authenticate via a web-session; and those permissions are specified/stored in the DB per user and don't have to be part of the auth token itself...

If so - then what might work is that in your identity_loaded method you could do:

if get_request_attr("fs_authn_via") == "token": adjust permissions as needed

Note that the user will already been granted the permissions as specified in their roles so you might have to remove some permissions... In this case the authentication will happen as part of the _request_loader, and you will have a chance to set the identities permissions PRIOR to any of the authorization decorators from being run.

N247S commented 1 year ago

Thanks again for your response.

That would work if the permission set was either fixed, or if only a single accesstoken was allowed. Also wouldn't this mess with the default token authentication?

In any case, I added the accesstoken specs to the original question.

jwag956 commented 1 year ago

Rewriting this comment from this morning (coffee has kicked in).

You can overload UserMixin::get_auth_token and verify_auth_token - and you should be able to add additional pieces to the authtoken as returned by Flask-Security. In verify_auth_token() you can check for your expiration, etc constraints.

verify_auth_token could de-code the authtoken and place whatever additional stuff you put in there onto 'g' so that _on_identity_loaded could grab it and set/unset appropriate permissions.

N247S commented 1 year ago

Thanks for the response, sorry I couldn't get back earlier.

That could work. I have to be carefull though not ot mess up the normal 'token' mechanisms. I'll try several things and see what works best.

Thanks for your input!

jwag956 commented 1 year ago

If you have more questions - please open a discussion....