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

Improve password reset scheme #4497

Closed
hugo opened this issue Nov 21, 2014 · 3 comments
Closed

Improve password reset scheme #4497

hugo opened this issue Nov 21, 2014 · 3 comments

Comments

@hugo
Copy link
Contributor

hugo commented Nov 21, 2014

As discussed in #4490, the current system could be improved. This is blocked on #4479, but once migrations are resolved, I'm happy to do the work to actually implement my proposal.

@sebgie
Copy link
Contributor

sebgie commented Nov 21, 2014

Proposal from #4490 (comment):

We move tokens to their own table. When a reset is requested we generate two tokens, one short used for lookup, one long used for auth. We of course ensure that the lookup token is unique in the token table. Both tokens are generated from URI safe characters, and we put them in the query string, not the path, because that's more idiomatic. The url would look something like:
http://www.example.com/reset?token=abcDEF123&auth_token=ghIJKLMnop4567QRS.

In the database we save, in a tokens table:

  • short token
  • email
  • expiry
  • requested_at (the time these tokens were generated)
  • encrypted auth token
  • the password hash of the user at the time this request was made

Before storing the token, we prune the table of expired tokens, then get a count of token requests for the submitted email. If there have been too many, we check if any of the tokens are old enough to delete (say, more than 10 minutes old), and check the count again. If it's still too high we deny the request, otherwise we do the token generation and sending.

When the user submits the two tokens, we fail if:

  • the lookup token doesn't exist (generic failure)
  • the auth token is invalid (generic failure)
  • the token has expired (explicitly report)
  • the user has already changed their password i.e. the users hash doesn't match the tokens hash (report?)

Otherwise, all is good and we save the new password.

Reasoning from #4490 (comment):

Well, there's the rub. It doesn't really add much benefit now.

  • I would argue that not exposing the password hash to the world is an upside, but as any attacker than can read the reset token can just set any password they like it doesn't really matter if they get a hash to try and brute force against.
  • This scheme is more extensible, so down the line if we want to, say, require that the reset request and submission come from the same IP, that's a trivial change.
  • Philosophically, I think this is a more correct way to do it, and as a high profile project Ghost should set good examples to people reading its code.

@hugo
Copy link
Contributor Author

hugo commented Nov 21, 2014

On further thought, it's possible that having a "reset token" store might be the wrong level of abstraction.

Cookies, oAuth, and password resets all work on the basis of having a password-equivalent token, that is submitted to the server. It might be worth having an over-arching "token store" that is responsible for issuing, revoking, and validating tokens. This store can be also handle the rate limiting work (n issues per delta-T, n auths per delta-T etc.). We then have a set of token "transports," e.g. cookie, email, that deliver that token to the user.

This actually reduces complexity, as rather than having multiple different places where tokens are generated there will be one, canonical source.

@ErisDS
Copy link
Member

ErisDS commented Jan 16, 2017

This is made redundant by the introduction of Ghost Auth.

@ErisDS ErisDS closed this as completed Jan 16, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants