Skip to content
This repository has been archived by the owner on Aug 3, 2020. It is now read-only.

Auth #1

Open
wants to merge 76 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
65f49a9
User registration
josevalim Mar 16, 2020
e84f9be
Login/logout
josevalim Mar 16, 2020
eff8376
Add user confirmation
josevalim Mar 17, 2020
d517b54
Use tokens for login/logout
josevalim Mar 17, 2020
64ed8f9
Password resets
josevalim Mar 23, 2020
d4ea2b4
E-mail change
josevalim Mar 23, 2020
b677abc
Update password
josevalim Mar 23, 2020
1befca8
Redirect to where the user was
josevalim Mar 23, 2020
6625019
Add remember me cookie
josevalim Mar 24, 2020
153b7ce
Remove outdated comments
josevalim Mar 24, 2020
fe52685
Initial batch of tests
josevalim Mar 24, 2020
3e30e6d
Always clear session on login/logout
josevalim Mar 25, 2020
5a2f0ed
More tests
josevalim Mar 25, 2020
d3b7a93
change password context test
josevalim Mar 25, 2020
653d4ed
Session tests
josevalim Mar 25, 2020
ba4b093
More tests and logic improvements
josevalim Mar 25, 2020
c157742
Domain tests done
josevalim Mar 25, 2020
f137822
UserAuth tests
josevalim Mar 25, 2020
5b550b8
Update test/demo/accounts_test.exs
josevalim Mar 25, 2020
e7dd9e1
Update lib/demo/accounts.ex
josevalim Mar 25, 2020
4e02461
Update lib/demo/accounts.ex
josevalim Mar 25, 2020
39ccccb
Initial controller tests
josevalim Mar 26, 2020
3558bac
Session controller
josevalim Mar 26, 2020
bdef99d
Registration controller
josevalim Mar 26, 2020
e61a7aa
Settings controller
josevalim Mar 26, 2020
fc2c735
Confirmation controller tests
josevalim Mar 26, 2020
76ff10a
Reset tests
josevalim Mar 26, 2020
44a6bac
Apply suggestions from code review
josevalim Mar 26, 2020
7c07c02
Add a license
josevalim Mar 26, 2020
085dd92
Use citext for PG
josevalim Mar 26, 2020
0e3efd9
Update lib/demo_web/router.ex
josevalim Mar 26, 2020
fcf4a8b
More validations as suggestions
josevalim Mar 26, 2020
9113466
Update test/demo_web/controllers/user_confirmation_controller_test.exs
josevalim Mar 26, 2020
0712d9c
Rename file properly
josevalim Mar 26, 2020
9c22870
Apply suggestions from code review
josevalim Mar 27, 2020
19470c8
Update lib/demo_web/templates/layout/_user_menu.html.eex
josevalim Mar 27, 2020
4796235
Tokens must be unique
josevalim Mar 30, 2020
f801205
Add tests, improve index
josevalim Mar 30, 2020
2a7616a
Return a mail structure from notifier
josevalim Mar 31, 2020
b53f616
Move durations to module attributes and settle in days
josevalim Mar 31, 2020
6049142
encrypted_password -> hashed_password
josevalim Apr 1, 2020
97bd003
Update lib/demo_web/controllers/user_auth.ex
josevalim Apr 1, 2020
7f3a55e
Apply suggestions from code review
josevalim Apr 1, 2020
f698bc8
Grammar
josevalim Apr 1, 2020
ef46ae1
Update test/demo/accounts_test.exs
josevalim Apr 1, 2020
d3aca05
Apply suggestions from code review
josevalim Apr 1, 2020
326db9c
More fixes
josevalim Apr 2, 2020
4abf899
Apply suggestions from code review
josevalim Apr 2, 2020
f3a1571
Update priv/repo/migrations/20200316103722_create_user_auth_tables.exs
josevalim Apr 9, 2020
8655c00
Update lib/demo/accounts/user.ex
josevalim Apr 9, 2020
5fbf0ba
Use distinct IDs
josevalim Apr 9, 2020
13de852
Delete password after hashing
josevalim Apr 9, 2020
6637c85
Update test/demo/accounts_test.exs
josevalim Apr 9, 2020
ead269e
Apply suggestions from code review
josevalim Apr 9, 2020
c73cba4
Add missing "for" attributes on password field labels (#2)
srgpqt Apr 10, 2020
01194c3
token -> link
josevalim Apr 11, 2020
288c67a
Apply suggestions from code review
josevalim Apr 15, 2020
9b1a270
Return user from confirm_user for convenience
josevalim Apr 15, 2020
cd2ab05
Apply suggestions from code review
josevalim Apr 16, 2020
9912c79
Update file
josevalim Apr 16, 2020
25d083d
Update test/demo_web/controllers/user_auth_test.exs
josevalim Apr 16, 2020
fbd52ec
Update lib/demo/accounts/user.ex
josevalim Apr 17, 2020
ecc8eb5
Update lib/demo/accounts.ex
josevalim Apr 17, 2020
6ae63ab
Only hash password when saving to database (#4)
aaronrenner Apr 28, 2020
e51d52a
Remove unused if
josevalim May 1, 2020
bb13f6b
maybe_hash_password -> hash_passwword
josevalim May 2, 2020
1eebcae
Set the live_socket_id on login and disconnect on logout
josevalim May 3, 2020
4113391
Use the subscribe from the endpoint (undeprecated on v1.5.2)
josevalim May 4, 2020
2b92a22
Remove unecessary user_ prefix
josevalim May 6, 2020
3003342
Comments
josevalim May 7, 2020
6f2ef5d
Logout should succeed even if the user already logged out
josevalim May 8, 2020
83d5deb
Update lib/demo/accounts.ex
josevalim May 8, 2020
78cb46e
Add user prefix to session token functions
josevalim May 17, 2020
57bf7df
Add index on user_id
josevalim May 19, 2020
ed0e210
Update lib/demo/accounts.ex
josevalim May 26, 2020
4664c37
Update test/support/fixtures/accounts_fixtures.ex
josevalim May 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
# Demo

To start your Phoenix server:
## Spec

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you see this spec going? Will it be in the Readme of phx_gen_auth, in the moduledoc of generated code, or just in this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally thought it would only be part of this PR but I think you will be more capable of assessing it. If you feel like there is important information in here, then maybe some of it should be added to mix phx.gen.auth docs OR added to the module docs of the related modules, especially the user and user_token ones. WDYT?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I'll look for a good place to put it. 👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this as @moduledoc is something that I wanted to suggest after running mix phx.gen.auth in my project. It would make it so much easier to read through the code, especially considering the fact that when it's generated via mix it's a single commit. I personally found it a bit hard to figure out where to start reading the generated code.


* Install dependencies with `mix deps.get`
* Create and migrate your database with `mix ecto.setup`
* Install Node.js dependencies with `npm install --prefix assets`
* Start Phoenix endpoint with `mix phx.server`
We will have two database tables: "users" and "users_tokens":

Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
* "users" will have the "hashed_password", "email" and "confirmed_at" fields plus timestamps
* "users_tokens" will have "token", "context", "sent_to", "inserted_at" fields
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered moving away from pre-required/specific database tables?

Consider the scenario where the user table already exists, so all they need to do is to add the missing columns and use your logic. This would virtually enable absolute flexibility and stay out of the end-user/dev way. For example, the way I look at this current implementation is that the spec assumes there will be a password always (which is not true).

For example, you could consider replacing the users_tokens with something like JWT. Where you sign (or even encrypt) the token payload. You decrypt/verify it in your logic instead of bringing the DB as a requirement.

I recently worked on a passwordless authentication solution, and it's unfortunate that most (if not all) the existing authentication systems are strictly following patterns that will always stay in your developer way and most probably enforce some pretty painful UX on the end-user side.

Copy link
Member Author

@josevalim josevalim Apr 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally have no interest in covering all possible use cases. :) I have done that, it didn't work. I intend this solution to be pretty much as is. If you want to move away from what it does, you can either:

  1. Use this generator and then modify the generated result accordingly
  2. Use this generator as a template for your own generator
  3. Don't use this generator at all and roll your own (or use a lib)

In regards to JWT and in general non DB-backed tokens, we discussed this extensively, and we believe that DB tokens are more secure and provide better UI, as they allow you to expire and track them individually or collectively.

In any case, I don't guarantee this generator provides the best UI possible, if you believe that passwordless is the best way to go for certain apps, then I look forward to a mix phx.gen.passless - you certainly already did the hardest part of the work. :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explaining this @josevalim

I arrived here having in mind Devise as a context. I'm glad this is a more specific solution vs. all-case one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, in case you didn't see it, there is more context here: https://dashbit.co/blog/a-new-authentication-solution-for-phoenix

I was not expecting folks to find this PR outside of the blog post. :)


Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
On sign in, we need to renew the session and delete the CSRF token. The password should be limited to 80 characters, the email to 160.

## Learn more
Confirmation, resetting passwords, remember me, and session management will be all based on tokens. Tokens are generated randomly using `:crypto.strong_rand_bytes/1` and then hashed using sha2/sha3. The hashing helps protect against timing attacks. The hashed token is the one stored in the database, the original token is not stored.

* Official website: https://www.phoenixframework.org/
* Guides: https://hexdocs.pm/phoenix/overview.html
* Docs: https://hexdocs.pm/phoenix
* Forum: https://elixirforum.com/c/phoenix-forum
* Source: https://github.com/phoenixframework/phoenix
Whenever a token is generated, a context has to be given (such as "session", "reset", etc). The context is used to avoid one token being used against its original purpose. Verifying the token will hash the token and look-up its hashed result in the database. Verification also takes a ttl. The current date is compared against the token `inserted_at + ttl` and the token is deemed as expired if enough time has passed.

For confirming e-mail addresses, we will generate a token, and e-mail it to the user. We will store the hashed token, the context ("confirm"), and store the confirmation e-mail under the `sent_to` column. Once the user clicks the link, we will verify the token, and see if the current user e-mail matches the one in the `sent_to` column. Once the token is used, it is deleted. Note we won't automatically sign the user in after confirmation - this protects us from someone getting access to the account via confirmation tokens. This also allows us to set long expiry dates in the tokens.

For resetting the password, the process is very similar. Once the link is clicked, it will go to the reset password page. The reset password page will ask for the new password and for the password confirmation. Once the user sends the form, we will verify the token and proceed to reset the password. Resetting the password will delete all tokens. Generally speaking, changing the password always deletes all tokens. Resetting the password won't sign the user in - so if anyone intercepts the token, they cannot gain access to the system as they are missing the e-mail/username.
Copy link

@djm djm Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resetting the password won't sign the user in - so if anyone intercepts the token, they cannot gain access to the system as they are missing the e-mail/username.

Hi @josevalim. This is super helpful! I just want a bit of clarification here: if we're worried about the token being leaked (for confirmation/password reset) then shouldn't we also be worried that we're advocating sending this token in the body of plain text email with a relatively long expiry (1 week)?

We can never guarantee secure transmission of email, and in the case of an intercepted email an attacker would also have access to the email address, so that is enough data for an account takeover using the password reset functionality as they have the 2 bits of info required (token + email) and can set their own password and login immediately regardless of whether we do it for them or not.

So I guess my questions are:

a) why would we take the UX-hit of not logging users in automatically given the most likely token leak scenario is via email, where the attacker would have access to the email address anyway?

b) would it be wise to shorten the token expiry here, to reduce the interception window? Django uses a default of 3 days, and allows automatic login after reset but both are configurable. Laravel uses a much stricter one hour window, but also automatically auths after resets. I guess you could say people can change it but the wording of the text above: "This also allows us to set long expiry dates in the tokens." seems to suggest people should push for longer token expiry.

It's late here and I may have missed something super obvious but thought I'd ask anyway.

Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think those are good considerations. I will reduce the initial period is a safe call. Thanks!


For changing the e-mail, the user will put the new e-mail in a field. This will create a new token with change:CURRENT_EMAIL as context and the new e-mail stored in the `sent_to` column. A hashed token will be sent to the new e-mail. Once the user clicks on the e-mail link, the raw token will be hashed and we will change the user to the new e-mail as long as the hashed token and old email match to the currently signed in user. Once this is done, the token is deleted. This token will have a short expiry date by default.

## Pending for generators

mix phx.gen.auth Account User users ...extrafields...

The authentication mechanism should be an option. Default to bcrypt for Unix systems, pdkdf2 for Windows systems. The line to config/test.exs must always be added.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though all are decent, Argon2(id) might be the better choice for new platforms. Bcrypt is starting to show flaws (and Pbkdf2 has been the weaker choice for a long time now). If I didn't conform to NIST/OWASP recommendations for Pow, then I would have gone with Argon2 as the default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that's why I was going with Bcrypt too. Have they updated their recommendation at all?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. Then this is good. AFAIK bcrypt is still default recommendation by OWASP and Pbkdf2 or Balloon by NIST.


## License

Copyright 2020 Dashbit

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
3 changes: 3 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ config :demo, DemoWeb.Endpoint,

# Print only warnings and errors during test
config :logger, level: :warn

# Only in tests, remove the complexity from the password hashing algorithm
config :bcrypt_elixir, :log_rounds, 1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should support all three comeonin adapters. We should default to bcrypt except on Windows which should use pkbdf2. Each adapter will require a different line here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argon2 is recommended here. Any reason not to use it as the default?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am aware folks will have different opinions and takes on the password hashing, that's why I prefer to follow recommendations from OWASP and similar and, AFAIK, it is still bcrypt. If someone has any updated insights or data on that, I would love to hear. :)

Copy link

@rafaelfess rafaelfess Apr 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the recommendation is still bcrypt, except in some cases, as stated in OWASP: Password Storage Cheat Sheet.

Bcrypt is the most widely supported of the algorithms and should be the default choice unless there are specific requirements for PBKDF2, or appropriate knowledge to tune Argon2.
The default work factor for Bcrypt is 10, and this should generally be raised to 12 unless operating on older or lower-powered systems.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any thoughts on passwordless auth? it solves a lot of hassle in pass world like reset, change, forgot, keeps email as source of truth and solve password hashing issues

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any thoughts on passwordless auth? it solves a lot of hassle in pass world like reset, change, forgot, keeps email as source of truth and solve password hashing issues

In my opinion, passwordless auth should be avoided most of the time. I guess it depends on your user base, but in my experience, many people use their work email to sign up for services, even on non work related sites. Unless you can verify their identity some other way during sign up, that means they will lose access to their accounts when they change jobs, since you can't trust email update requests from any other email address. This is especially important if your project falls under GDPR.

Copy link

@Rovel Rovel Jun 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, passwordless auth should be avoided most of the time. I guess it depends on your user base, but in my experience, many people use their work email to sign up for services, even on non work related sites. Unless you can verify their identity some other way during sign up, that means they will lose access to their accounts when they change jobs, since you can't trust email update requests from any other email address. This is especially important if your project falls under GDPR.

even in that cenario, its safer passwordless since the emails belongs to the company and that could be abused by bad actors, I manage and managed the email myself a couple of times, its usual to help someone to recover stuff left in company emails they dont own anymore, passwords dont solve this issue, specially for car rentals, and some other kind of stuff they get important purchase info by mail, the mistake is to use a email you dont own, and when you know is passwordless you think twice to use one you do not own.
I understand that a lot of work was put in this pr but if you think about future solutions of auth, passwordless is one of the most promising for a lot of reasons.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone is welcome to implement mix phx.gen.passwordless. :) This can be a great starting point.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading