Skip to content
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

Authtokens are slow #156

Closed
jwag956 opened this issue Aug 9, 2019 · 12 comments · Fixed by #164
Closed

Authtokens are slow #156

jwag956 opened this issue Aug 9, 2019 · 12 comments · Fixed by #164
Assignees
Labels

Comments

@jwag956
Copy link
Collaborator

jwag956 commented Aug 9, 2019

This has caused more folks to abandon flask-security that anything.

pallets-eco/flask-security-3.0#731

cross posted:
https://stackoverflow.com/questions/47157092/slow-flask-response-when-using-flask-security

The issue is that FS is using default hashing per-request to verify that the data in the auth token corresponds to the stored user password.

This seems totally wrong - auth tokens shouldn't be invalidated just because someone changes their password

Also - while the UserMixin has a method for get_auth_token - it doesnt easily allow overriding verifying auth_token - so it is almost impossible to do one's on thing.

Tokens are already signed - that should be enough.

@jwag956 jwag956 added the bug label Aug 9, 2019
@jwag956 jwag956 self-assigned this Aug 9, 2019
@acidjunk
Copy link

acidjunk commented Aug 9, 2019

FWIW, I developed a workaround with a second faster token. Not sure if this is something that will work for everyone. Posted an explanation on Stack Overflow.

https://github.com/acidjunk/improviser/blob/master/improviser/security.py

More than happy to discuss/help developing an improvement for authenticating REST request in a faster way.

@jwag956
Copy link
Collaborator Author

jwag956 commented Aug 9, 2019

Thanks @acidjunk for your comments and code. Here's the thing - and I am interested in your thoughts since I might be missing some use cases.

I believe that browser-based access/UIs should use sessions - not tokens. sessions are more secure (and already handled out of the box). You said you have a SPA - I'd be interested in why you decided to use tokens rather than the existing session cookie? (I converted my SPA to just use cookies).

For scripts, service to service - tokens need to be used.
The current tokens are cryptographically signed (using itsdangerous) - so the server knows and can trust that if the token is valid, that the contents haven't been tampered with.

There seem to be 2 things the current implementation does:

  1. the hashed passwd is a uniquifier of sorts - since the user.id may or may not be recycled.
  2. provides a way to 'revoke' tokens - by changing the password.

So my approach is to add a 'uniquifier' - a uuid to the user model. That uniquifier is returned as part of the (signed) token. On request, a simple string compare makes sure that the user.id hasn't been recycled. By changing the user.uniquifier, you can immediately 'revoke' existing tokens.

Can you think of any down sides to that?

@acidjunk
Copy link

I’m building the improviser for fun and to explore the world of micro services. That being said I’ll probably move the login to a separate micro service soon. So that’s why I wanted to use token based auth.

I suppose there are 2 different use cases for flask-security-too to consider: a cookie based login for “normal” web apps and flask-admin + a simpler token based authentication system for REST requests for scripts and service to service.

I like your proposed approach as it will be fast. The only downside I see is when a hacker has access to the DB he/she could use it to login without much effort. I’m not a security expert though. Maybe it would be a good idea to hash the uniqifier a bit, and maybe even let it expire after some time (which seems a bit impractical for service to service). Probably practical a feature or setting that the user can re-login to get a new token for some sub use cases.

I don’t mind studying some OAUTH designs a bit as they seem to implement fast token based systems. Let me know if you would to cooperate on this.

Very nice that you are looking into this, as this seems a bit of a disappointment for users who run into the slow token problem.

@jwag956
Copy link
Collaborator Author

jwag956 commented Aug 11, 2019

Thanks for your comments. The uniqueifier is just that. You get a new signed (timed) token each time you ask for it. So hopefully in most cases, applications will set a timeout for tokens.
As for security - if a hacker gets read access to your DB - they can see the uniquefier - but unless that have the SECRET_KEY - they can't construct a valid token.If they break into your app and get the SECRET_KEY presumably they then have credentials to your DB - so pretty much it's a lost cause.

@acidjunk
Copy link

Ok great.

There might be another use-case, but I am not sure if this is something flask-security should support (might be something that users have to create themselves if they want it). There are systems, like Topdesk, that uses cookie login and where the user can generate a token that can be used to query the API. That token doesn't expire, but can be revoked by the user from the main webpage.

jwag956 added a commit that referenced this issue Aug 14, 2019
Current auth tokens are slow because they use the user's password (hashed) as a uniquifier (the
user id isn't really enough since it might be reused). This requires checking the (hashed) password against
what is in the token on EVERY request - however hashing is (on purpose) slow. So this can add almost a whole second
to every request!

This PR introduces a new UserModel field - fs_uniquifier - that if present in the UserModel will be populated and used rather than the password. This results in 50x reduction in time when authenticating via token.

Furthermore, the actual token verification has been moved from request_loader into the UserMixin - so
that it could be overridden (creating the auth_token already was in the UserMixin).

Note that this does require a DB migration to add the field. The fsqla model has been updated, and docs
describing at least one way to migrate the DB have been added.

2 new backwards compatibility configurations have been added that can revert some new default behavior.

First - in the past- the auth token was included always in JSON responses to login, reset and change -
even if the caller was a browser. This is
really not great since auth tokens may have very long expire times (or none) and it shouldn't even be sent if not needed.
Now, by default, the auth token is NEVER returned - the caller may request is during login, reset, or change by adding the
'include_auth_token' query param.

Second, since auth tokens used to be checked against the hashed password - changing a user's password meant that any
outstanding auth tokens would be invalidated. That seems like strange behavior - so by default, tokens that are verified
with the new fs_uniquifier won't be invalidated just because the user's password changes. The
BACKWARDS_COMPAT_AUTH_TOKEN_INVALID config variable will cause the fs_uniquifier to be changed whenever the user's password changes, thus restoring the older behavior.

closes: #156
@jwag956
Copy link
Collaborator Author

jwag956 commented Aug 14, 2019

@acidjunk I just put up a PR that hopefully solves this - if you have time/inclination an extra pair of eyes on the core pieces would be great.

As for second point - 'cookie login' how is that different than the current FS 'passwordless login' feature which has been there for a long time. The only limitation today is that you can't have BOTH regular login and passwordless login (and there is an open issue around that).

jwag956 added a commit that referenced this issue Aug 15, 2019
Current auth tokens are slow because they use the user's password (hashed) as a uniquifier (the
user id isn't really enough since it might be reused). This requires checking the (hashed) password against
what is in the token on EVERY request - however hashing is (on purpose) slow. So this can add almost a whole second
to every request!

This PR introduces a new UserModel field - fs_uniquifier - that if present in the UserModel will be populated and used rather than the password. This results in 50x reduction in time when authenticating via token.

Furthermore, the actual token verification has been moved from request_loader into the UserMixin - so
that it could be overridden (creating the auth_token already was in the UserMixin).

Note that this does require a DB migration to add the field. The fsqla model has been updated, and docs
describing at least one way to migrate the DB have been added.

2 new backwards compatibility configurations have been added that can revert some new default behavior.

First - in the past- the auth token was included always in JSON responses to login, reset and change -
even if the caller was a browser. This is
really not great since auth tokens may have very long expire times (or none) and it shouldn't even be sent if not needed.
Now, by default, the auth token is NEVER returned - the caller may request is during login, reset, or change by adding the
'include_auth_token' query param.

Second, since auth tokens used to be checked against the hashed password - changing a user's password meant that any
outstanding auth tokens would be invalidated. That seems like strange behavior - so by default, tokens that are verified
with the new fs_uniquifier won't be invalidated just because the user's password changes. The
BACKWARDS_COMPAT_AUTH_TOKEN_INVALID config variable will cause the fs_uniquifier to be changed whenever the user's password changes, thus restoring the older behavior.

closes: #156
jwag956 added a commit that referenced this issue Aug 15, 2019
Current auth tokens are slow because they use the user's password (hashed) as a uniquifier (the
user id isn't really enough since it might be reused). This requires checking the (hashed) password against
what is in the token on EVERY request - however hashing is (on purpose) slow. So this can add almost a whole second
to every request!

This PR introduces a new UserModel field - fs_uniquifier - that if present in the UserModel will be populated and used rather than the password. This results in 50x reduction in time when authenticating via token.

Furthermore, the actual token verification has been moved from request_loader into the UserMixin - so
that it could be overridden (creating the auth_token already was in the UserMixin).

Note that this does require a DB migration to add the field. The fsqla model has been updated, and docs
describing at least one way to migrate the DB have been added.

2 new backwards compatibility configurations have been added that can revert some new default behavior.

First - in the past- the auth token was included always in JSON responses to login, reset and change -
even if the caller was a browser. This is
really not great since auth tokens may have very long expire times (or none) and it shouldn't even be sent if not needed.
Now, by default, the auth token is NEVER returned - the caller may request is during login, reset, or change by adding the
'include_auth_token' query param.

Second, since auth tokens used to be checked against the hashed password - changing a user's password meant that any
outstanding auth tokens would be invalidated. That seems like strange behavior - so by default, tokens that are verified
with the new fs_uniquifier won't be invalidated just because the user's password changes. The
BACKWARDS_COMPAT_AUTH_TOKEN_INVALID config variable will cause the fs_uniquifier to be changed whenever the user's password changes, thus restoring the older behavior.

closes: #156
jwag956 added a commit that referenced this issue Aug 15, 2019
Current auth tokens are slow because they use the user's password (hashed) as a uniquifier (the
user id isn't really enough since it might be reused). This requires checking the (hashed) password against
what is in the token on EVERY request - however hashing is (on purpose) slow. So this can add almost a whole second
to every request!

This PR introduces a new UserModel field - fs_uniquifier - that if present in the UserModel will be populated and used rather than the password. This results in 50x reduction in time when authenticating via token.

Furthermore, the actual token verification has been moved from request_loader into the UserMixin - so
that it could be overridden (creating the auth_token already was in the UserMixin).

Note that this does require a DB migration to add the field. The fsqla model has been updated, and docs
describing at least one way to migrate the DB have been added.

2 new backwards compatibility configurations have been added that can revert some new default behavior.

First - in the past- the auth token was included always in JSON responses to login, reset and change -
even if the caller was a browser. This is
really not great since auth tokens may have very long expire times (or none) and it shouldn't even be sent if not needed.
Now, by default, the auth token is NEVER returned - the caller may request is during login, reset, or change by adding the
'include_auth_token' query param.

Second, since auth tokens used to be checked against the hashed password - changing a user's password meant that any
outstanding auth tokens would be invalidated. That seems like strange behavior - so by default, tokens that are verified
with the new fs_uniquifier won't be invalidated just because the user's password changes. The
BACKWARDS_COMPAT_AUTH_TOKEN_INVALID config variable will cause the fs_uniquifier to be changed whenever the user's password changes, thus restoring the older behavior.

closes: #156
jwag956 added a commit that referenced this issue Aug 15, 2019
Current auth tokens are slow because they use the user's password (hashed) as a uniquifier (the
user id isn't really enough since it might be reused). This requires checking the (hashed) password against
what is in the token on EVERY request - however hashing is (on purpose) slow. So this can add almost a whole second
to every request!

This PR introduces a new UserModel field - fs_uniquifier - that if present in the UserModel will be populated and used rather than the password. This results in 50x reduction in time when authenticating via token.

Furthermore, the actual token verification has been moved from request_loader into the UserMixin - so
that it could be overridden (creating the auth_token already was in the UserMixin).

Note that this does require a DB migration to add the field. The fsqla model has been updated, and docs
describing at least one way to migrate the DB have been added.

2 new backwards compatibility configurations have been added that can revert some new default behavior.

First - in the past- the auth token was included always in JSON responses to login, reset and change -
even if the caller was a browser. This is
really not great since auth tokens may have very long expire times (or none) and it shouldn't even be sent if not needed.
Now, by default, the auth token is NEVER returned - the caller may request is during login, reset, or change by adding the
'include_auth_token' query param.

Second, since auth tokens used to be checked against the hashed password - changing a user's password meant that any
outstanding auth tokens would be invalidated. That seems like strange behavior - so by default, tokens that are verified
with the new fs_uniquifier won't be invalidated just because the user's password changes. The
BACKWARDS_COMPAT_AUTH_TOKEN_INVALID config variable will cause the fs_uniquifier to be changed whenever the user's password changes, thus restoring the older behavior.

closes: #156
@acidjunk
Copy link

@jwag956 damn you're fast: merged already :)
I will try the changes early next week.

Only found some very minor, nitpicking, mini typo's in the docs.

SideNote:
I think that invalidating the token upon pass change would be wise. Not sure if this should happen for all use cases, but maybe it should be configurable.

@jwag956
Copy link
Collaborator Author

jwag956 commented Aug 16, 2019

Wanted to get it in - however - I believe you can still do a review and put in comments/things you find or think aren't right. I am a believer in post-merge reviews.. So if you have time - please 'review' the PR and I will fix them.. Thanks.

There is a config that will cause tokens to be invalidated on password change - it isn't the default.
My thinking is, in normal websites - one uses user/pass to log in, and then generates API keys/tokens for service/API requests. those aren't connected at all (certainly on github if you change your password your ssh keys aren't revoked).

Now, I do understand that in this case - we aren't tracking the tokens as they are in oauth2 'client credentials' etc. So that is a big difference - which is why I put in the config variable.

@acidjunk
Copy link

Added some minor comments tot the PR. I will try it in my stack and will probably create a small PR to add some extra stuff to the docs as my SPA finally has decent pass reset etc. I will try to document some specific examples and learnings for SPA usage. Thanks for your dedication and time, I'm really happy with the momentum!

@jwag956
Copy link
Collaborator Author

jwag956 commented Aug 21, 2019

@acidjunk Did you actually add some comments? I can't seem to find them.
Thanks for trying this out - we really need additional coverage...

@acidjunk
Copy link

Yes in the PR:
Screenshot 2019-08-22 at 12 22 39

Scheduled rewrite of my SPA on Tuesday: will document it, and create a mini PR to add some stuff to the docs.

@jwag956
Copy link
Collaborator Author

jwag956 commented Aug 22, 2019

Interesting - I am not seeing that - possibly due to it saying 'pending'?
Looking forward to hearing how your 'rewrite' etc goes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging a pull request may close this issue.

2 participants