Ecto is configured, but I don’t have any records that are stored in a database. User records are currently stored in memory so every deployment will dump the user store. These ephemeral records will be the first to be migrated to database persistence.

Phoenix comes with a generator for creating an initial authentication system complete with web forms, session tokens, and an Ecto migration file. Since I already have an existing authentication system, I create a separate Phoenix application (mix phx.new hello) from which to run the generator to compare the files with my existing project.

I run the command mix phx.gen.auth Accounts User users which generates the following output:

* creating priv/repo/migrations/20240706185614_create_users_auth_tables.exs
* creating lib/hello/accounts/user_notifier.ex
* creating lib/hello/accounts/user.ex
* creating lib/hello/accounts/user_token.ex
* creating lib/hello_web/user_auth.ex
* creating test/hello_web/user_auth_test.exs
* creating lib/hello_web/controllers/user_session_controller.ex
* creating test/hello_web/controllers/user_session_controller_test.exs
* creating lib/hello_web/live/user_registration_live.ex
* creating test/hello_web/live/user_registration_live_test.exs
* creating lib/hello_web/live/user_login_live.ex
* creating test/hello_web/live/user_login_live_test.exs
* creating lib/hello_web/live/user_reset_password_live.ex
* creating test/hello_web/live/user_reset_password_live_test.exs
* creating lib/hello_web/live/user_forgot_password_live.ex
* creating test/hello_web/live/user_forgot_password_live_test.exs
* creating lib/hello_web/live/user_settings_live.ex
* creating test/hello_web/live/user_settings_live_test.exs
* creating lib/hello_web/live/user_confirmation_live.ex
* creating test/hello_web/live/user_confirmation_live_test.exs
* creating lib/hello_web/live/user_confirmation_instructions_live.ex
* creating test/hello_web/live/user_confirmation_instructions_live_test.exs
* creating lib/hello/accounts.ex
* injecting lib/hello/accounts.ex
* creating test/hello/accounts_test.exs
* injecting test/hello/accounts_test.exs
* creating test/support/fixtures/accounts_fixtures.ex
* injecting test/support/fixtures/accounts_fixtures.ex
* injecting test/support/conn_case.ex
* injecting config/test.exs
* injecting mix.exs
* injecting lib/hello_web/router.ex
* injecting lib/hello_web/router.ex - imports
* injecting lib/hello_web/router.ex - plug
* injecting lib/hello_web/components/layouts/root.html.heex

Here is the schema from the generated lib/hello/accounts/user.ex:

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :current_password, :string, virtual: true, redact: true
    field :confirmed_at, :utc_datetime

    timestamps(type: :utc_datetime)
  end

This schema is not compatible with my existing authentication system as I’m using a username field instead of email. Since my application is an online game where users will be interacting with each other, I want each user to have a unique username which can be used a public identifier within the system. Some of the benefits of the generated authentication system using email are account confirmations and password resets which are bundled with the generated code. I decide I want to have both username and email fields for all users, but they will use email for session logins.

There are a lot of generated files that I want to merge with my project and I don’t want to break my application as I work on implementing these changes. Working in small steps, I will add bits of functionality at a time while making sure the tests pass and the application is stable.

I split the problem into two major changes: implementing authentication with email and implementing database persistence for user records. I won’t worry about database persistence until I’ve updated the authentication interface.

My plan is to work through the tests in the generated files and implement them in my project. I’ll work through the tests one at a time, adding any necessary logic to the code to make the test suite pass, and committing the changes when tests are all green.

I update my existing test for register_user to assert that an error is returned when email is missing. I don’t bother checking for validity or uniqueness yet, since I just want to make this one test pass. The test fails as expected.

I update the Minotaur.Accounts.User module to add email as a required field for registration changesets which are referenced by register_user. The test now passes, but other tests are failing in my test suite which depend on register_user.

A large number of these tests are fixed after simply updating my user_fixture helper function which is imported to most test files that interact with users. The other failing tests are feature tests which run simulated user interactions through ChromeDriver. The registration form used by these tests don’t have an email field so I add that next.

The test suite is now passing with this change. Email is required during registration, but it isn’t used anywhere and there isn’t any validation on the input. This is fine since important thing is the application is not in a broken state. I commit these changes before jumping to the next test to pull in from the generated files.

I finish updating register_user by copying tests for the email validations and implementing the logic in the modules. Since I haven’t yet migrated the user record creation to store in a database, I can’t follow the exact code from the generated authentication system and I’ll have to go back to update uniqueness validation as part of the database implementation when I get to that point.