When I first heard about Ash Framework at ElixirConf many years ago, I ignored it as a niche within a niche. I am often skeptical of this sort of thing because inevitably you will need to go off the “happy” path and it’s either impossible or painful. However, at the 2025 ElixirConf I sat in a talk about Ash and was intrigued by what I saw. I preordered the Ash Framework book and then mostly forgot about it until the book arrived!

I read the book straight through without touching a keyboard. The concepts were well explained and easy to understand. With the book read, I decided to start an app to play with.

the ash installer

The Ash site has a cool “installer” that let’s you enter a name, select some options, and generate a single command to get up and running.

I went with the default options except I added Password auth.

Ash Installer

Running this command created a new Phoenix app called Rails, used igniter to install Ash & the other selected/required libraries, set up the initial database, and ran the migrations.

We now have an app we can start adding resources to. However, I was mostly curious how the auth stuff was implemented.

the login form

Ash Login Form

By default Ash handles the rendering of the account-related forms for registering, logging in, etc. These work well enough for an internal app, but will need to be customized for a real app. Let’s try to customize the login form and see how it goes.

customizing ash’s login form

You can customize AshAuthentication Phoenix’s (APP) forms using overrides. The Rails app comes with all this wired up and we can start overriding in lib\rails_web\auth_overrides.ex.

APP has the concept of a Banner that appears above the sign-in form. Here we customize it to hide the image, make the text larger, and change it to “Let’s sign in!”.

defmodule RailsWeb.AuthOverrides do
  use AshAuthentication.Phoenix.Overrides

  override AshAuthentication.Phoenix.Components.Banner do
    set :image_url, nil
    set :text_class, "text-xl"
    set :text, "Let's sign in!"
  end
end

Pretty sweet!

However, we’ve also hit the first example of not being on the happy path. We want to customize the banner text on each page. Sign-in should say “Sign in to Rails”, password reset should say “Reset your password”, and so on. Turns out, that’s not really possible. APP uses one Banner component across all authentication pages. The component doesn’t know which page it’s on. It just renders the same text everywhere.

My first thought it to make the Banner component smart. Maybe it could detect the current URI and show different text based on the page. Nope, it won’t work. The Banner component doesn’t have access to the current URI. The override system doesn’t pass that information down.

As a workaround, we can creat separate Banner override modules for each authentication route. Each module sets its own text. Then we configured the router to use the right override for each route:

scope "/", RailsWeb do
    ...
    sign_in_route register_path: "/register",
      overrides: [RailsWeb.AuthOverrides.SignInBanner]
      ...

    reset_route auth_routes_prefix: "/auth",
      overrides: [RailsWeb.AuthOverrides.ResetBanner]
      ...

    confirm_route Rails.Accounts.User, :confirm_new_user,
      overrides: [RailsWeb.AuthOverrides.ConfirmBanner]
      ...
    ...
 end

Pretty sweet!

But we’ve hit another snag! Sign-in and register forms share the same route in APP. They use the register_path option to handle both forms on one route. So we can’t have different banner text for sign-in vs register. They have to share.

I couldn’t find a good way around this without implementing fully custom forms. For now, we have to compromise with “Welcome to Rails”. It works for both new and returning users.

final thoughts

You get a lot out of the box and it really has that Ruby on Rails feel. My fear about this type of thing boxing you into a corner seem to be well founded. Like Rails, you can move fast if you stay on the happy path, but once you venture off that sacred sidewalk and try to do something weird (i.e., pretty much anything), you’ll find out you can’t. You’re stuck on that sidewalk forever.