How do you authorize associations from user form data?

Sometimes you require foreign key ids to be included in the user form data.

Imagine a dropdown list where the user has to select a “location” when creating an “event”. His locations are saved and the dropdown shows a limited list of locations available.

Context function might typically look like:

def create_event(attrs) do
  %Event{}
  |> Event.changeset(attrs)
  |> Repo.insert()
end

where the event changeset accepts a location_id key.

However, the user could send an ID not available from the dropdown (bypassing the form input); even a location ID that doesn’t belong to him but to another user from the database.

There would need to be some sort of authorization for all those relations coming from external user data.

How do/would you manage that in your application? Where do you enforce authorization?

My take: the context functions should take the current user and the authorization logic belongs in the context. (this logic shouldn’t be in the controller/resolvers/etc. because it would be needed in multiple places and could be forgotten; implementing it in the contexts gives more guarantees).

But it means that most of my context functions will take some current-user struct and I’m not sure this is a commonly seen pattern.

2 Likes

FWIW, passing a user (or in one of the cases, a team) into context functions is a pattern I have seen in at least 2 codebases/companies.

2 Likes

Depending on your domain, consider making create_event take an already-found Location:

def create_event(location, attrs) do
  %Event{location: location}
  |> Event.changeset(attrs)
  |> Repo.insert()
end

As a bonus, Event.changeset doesn’t need to cast location_id at all - useful if "reassigning location_id" isn’t a meaningful operation in your domain.

You can consider using validate_change/3

You can validate based on the user_id and location_id in the attrs

1 Like