Contexts and auth

I’m building an app in Phoenix 1.3 and I’m struggling with decisions related to contexts. Specifically, I don’t understand how user schemas in multiple contexts should interact with auth.

I’m using Guardian for authentication and overriding controller __action__ to pipe around my current_user to my controller actions as an additional argument.

def new(conn, params, current_user) do
end

Above, current_user is fetched from Guardian and loaded into assigns using a router plug.

When I started writing the app I had one context, Accounts, and current_user was an %App.Accounts.User{} struct.

+ app
  + accounts
    - accounts.ex
    - user.ex
    - media.ex

My Accounts context became bloated and I extracted a Registration context to handle user registration as it has specific oauth logic useless elsewhere. Both user schemas use the same database table.

+ app
  + accounts
    - accounts.ex
    - user.ex
    - media.ex
  + registration
    - registration.ex
    - user.ex

One side effect is the ability to log in as %Registration.User{} or %Accounts.User{}. The current_user being piped to my controller actions from Guardian can be either struct, but they have different behaviours, associations, etc…

I wrote multiple function heads in guardian_serializer.ex to deal with one situation where I need to log the user in before continuing with registration. It seems odd, though.

  def for_token(user = %Accounts.User{}) do
    {:ok, "AccountsUser:#{user.username}"}
  end
  def for_token(user = %Registration.User{}) do
    {:ok, "RegistrationUser:#{user.username}"}
  end

  def from_token("AccountsUser:" <> username) do
    {:ok, Repo.get_by(Accounts.User, username: username)}
  end
  def from_token("RegistrationUser:" <> username) do
    {:ok, Repo.get_by(Registration.User, username: username)}
  end

I imagine this will become more complicated as I add contexts. The current_user being piped around needs to have the correct behaviour and associations to interact with the rest of the context. It doesn’t seem like multiple user structs go well with auth and multiple contexts.

What do you think?

1 Like

I think this is a wrong approach. Are those really separate contexts? If the problem is a module being too large, I see two solutions:

  • keep just a single context, but carve out a separate module for registration - e.g. Accounts.Registration and defdelegate functions from the main context to there.
  • have two separate contexts, but at the end of the registration, call the Accounts context to authenticate the user. This way the registration context depends on accounts (which makes sense) and allows you to keep the same data structures, while having separate implementations.

I believe the problem stems from the fact that you have duplicate logic - both Accounts and Registration now know how to authenticate a user, and this shouldn’t be the case. All business logic should have just one place.

2 Likes

I agree with you and I naturally ended up going down that road. I think the hardest part transitioning 1.3 is deciding on your boundaries. Sometimes, or early on in development, it may not be immediately clear where these boundaries are.

1 Like

Yeah I think the possibility of multiple contexts is being interpreted as a necessity for multiple contexts, even when someone only has a half dozen models that form a core part of their business logic. Starting an app with just 1 context is fine, just be ready to evolve it in time.

3 Likes