alexslade

alexslade

Alternative title: A yearning for mackerel

I have a specific problem (I want to load a :team resource on the current_user that AshAuthentication puts into conn assigns. I could do this as a follow-on query, but I’m trying to do it in one).

But really, I want to learn to fish solve this myself.

I tried adding a preparation, at first to the global preparations, and later to get_by_subject which seems to be used by the password strategy.

Either way, I get errorless redirects back to the sign-in page as if I wasn’t logged in. I can’t see anything with logging set to :debug, I can’t see anything relevant with log_successful_policy_breakdowns or policy_breakdowns turned on.

What are my next steps for finding out why ash_auth is failing here? I’m assuming there must be another way to get to an error code, though I accept it may be something that I simply need to understand and avoid. (I also assume there may be a better way of doing this team load).

Any tips are much appreciated.

Showing Posts 1 to 6

barnabasJ

barnabasJ

Ash Core Team

Not seeing any logs seems a bit strange.

I assume you have some policies for your team, because AshAuth calls the read action with a private context set that uses the bypass policy set up by AshAuth

    bypass AshAuthentication.Checks.AshAuthenticationInteraction do
      authorize_if always()
    end

# in the policy  
def match?(_, %{subject: %{context: %{private: %{ash_authentication?: true}}}}, _), do: true

So, there is not really an actor at that point, which could lead to the load failing. You could use the accessing_from policy to allow a user to read their team if they are loading through a relationship

policy action_type(:read) do
  authorize_if accessing_from(User, :team)
end

And than the LiveUserAuth module would redirect because there is no user.

What did you set log_successful_policy_breakdowns / policy_breakdown to? I think it has to be a log level.

Otherwise, using a preparation for this is a valid approach.

alexslade

alexslade OP

After going through the source a bit more and experimenting, I found the issue. Our Team resource has multi-tenancy turned on. I’ve fixed the issue now (setting tenant in our router) but I’m still keen to learn how I could have debugged this better.

As it stands, I got zero warnings about the multitenancy issue (even with all debug stuff on), so maybe ash_auth is incorrectly swallowing a warning somewhere? (If I can work out where, I’m happy to PR something)

I’ll paste the configs and logs to demonstrate.

I have a user (99% setup by the ash_auth igniter)

  # user.ex
...
  policies do
    bypass AshAuthentication.Checks.AshAuthenticationInteraction do
      authorize_if always()
    end
    
    # I have temporarily set this policy to always allow to rule it out of debugging
    policy always() do
      authorize_if always()
    end
  end
...
actions do
  ...
  read :get_by_subject do
    description "Get a user by the subject claim in a JWT"
    argument :subject, :string, allow_nil?: false
    get? true
    prepare AshAuthentication.Preparations.FilterBySubject
    prepare build(load: :team) # This is the problematic line
  end
  ...
# dev.exs
...
config :logger, level: :debug
config :ash, :policies, log_policy_breakdowns: :debug
config :ash, :policies, log_successful_policy_breakdowns: :debug
config :ash, :policies, show_policy_breakdowns?: true
...

Here are the full logs if I don’t have the prepare statement active.

[info] GET /
[debug] Processing with WinkWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser, :require_session, :require_team]
[debug] Successful authorization: Wink.Accounts.User.get_by_subject


Policy Breakdown
unknown actor

  Bypass: Policy | 🌟:

    condition: AshAuthentication is performing this interaction

    authorize if: always true | ✓ | 🌟

  Policy | 🌟:

    condition: always true

    authorize if: always true | ✓ | 🌟


[debug] QUERY OK source="users" db=0.3ms idle=1002.1ms
SELECT u0."id", u0."role", u0."email", u0."team_id", u0."hashed_password" FROM "users" AS u0 WHERE (u0."id"::uuid = $1::uuid) ["8e59877b-82d6-4379-ac11-deeb88c8f99a"]
↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:767
[info] Sent 200 in 6ms

Here are the logs if I do have the prepare statement active

[info] GET /
[debug] Processing with WinkWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser, :require_session, :require_team]
[debug] Successful authorization: Wink.Accounts.User.get_by_subject


Policy Breakdown
unknown actor

  Bypass: Policy | 🌟:

    condition: AshAuthentication is performing this interaction

    authorize if: always true | ✓ | 🌟

  Policy | 🌟:

    condition: always true

    authorize if: always true | ✓ | 🌟


[debug] QUERY OK source="users" db=0.2ms idle=1292.0ms
SELECT u0."id", u0."role", u0."email", u0."team_id", u0."hashed_password" FROM "users" AS u0 WHERE (u0."id"::uuid = $1::uuid) ["8e59877b-82d6-4379-ac11-deeb88c8f99a"]
↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:767
[info] Sent 302 in 2ms
[debug] Phoenix.Router halted in :require_session/2
# At this point, the conn has no `current_user` and so gets bounced by our checks for current user 

What I can see is that the same SQL is run, the same policy is run (and passes), but I mysteriously don’t have a current_user being set on the conn. As I said above, I assume an Ash.Error.Invalid.TenantRequired is being thrown somewhere but caught by AshAuth.

barnabasJ

barnabasJ

Ash Core Team

I think this happens here:

https://github.com/team-alembic/ash_authentication_phoenix/blob/4a43c238c7de711551fc86d0a535c2dbd186cac0/lib/ash_authentication_phoenix/live_session.ex#L119

This calls the action on the user resource internally, and if that returns an error, nil is set as the user. It seems the tenant error is detected before any policies are run, so you don’t get a log for the team.

Not sure about how you could debug this better. But here is the approach I took.

I looked at it more like a regular bug than anything Ash specific. From the little LiveView experience I had, I knew that the user would be most likely set in a live_session. So i looked at the router setup and saw the ash_authentication_live_session setup by the ash_auth igniter and went to it’s definition. There I saw that the Module was added to the list of on_mount hooks. That lead me to look at the on_mount function in the module.
I saw the call to AshAuthentication.subject_to_user and looked at that function. There I saw the Ash.Query being constructed and the read being called.

I saw that there was this special context that would allow to bypass the policies and that there is no real actor yet. This is what I put in my first answer.

barnabasJ

barnabasJ

Ash Core Team

As a next step I would add a debug statement there. To see what is actually returned from the read.

I’m not sure how you would integrate the tenant here, though. Maybe instead of having just a preparation that adds the load, you create your own that looks at the subject to get the tenant and adds the load with an initial query that sets the tenant.

alexslade

alexslade OP

Thanks Barnaby, I may PR a logger statement there or something so that failure errors are at least logged.

No next step needed on the issue itself, I solved the tenancy issue with Ash.PlugHelpers.set_tenant in the router, this is picked up by AshAuth.

Thanks for your help :heart:

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews