mattei

mattei

Hi! I’m working on changing phx.gen.auth to work with JSON only. It’s going stellar, but I’ve faced an issue and I was wondering if someone could tell me what’s going on under the hood.

I’ve modified the :api plug to use sessions, and that’s going fine. I intend to use session tokens to authenticate. All views were changed to respond with JSON (using controller_json.ex files). My plug looks like this:

  pipeline :api do
    plug :accepts, ["json"]
    # Use the same plugs for authentication as the browser pipeline.
    plug :fetch_session
    plug :put_secure_browser_headers
    plug :fetch_current_user
  end

Note the absence of any flash plugs–being an API, this isn’t needed here.

Now, while transitioning a controller to return JSON, everything was going fine until this code:

  def create(conn, %{"user" => %{"email" => email}}) do
    if user = Accounts.get_user_by_email(email) do
      Accounts.deliver_user_confirmation_instructions(
        user,
        &url(~p"/users/confirm/#{&1}")
      )
    end

    render(conn, :create)
  end

When calling this controller, the request went through, but I ended up with this error:

Server: localhost:4000 (http)
Request: POST /accounts/confirm/
** (exit) an exception was raised:
    ** (ArgumentError) flash not fetched, call fetch_flash/2
        (phoenix 1.7.10) lib/phoenix/controller.ex:1686: Phoenix.Controller.put_flash/3
        (timeline 0.1.0) lib/timeline_web/controllers/user_confirmation_controller.ex:19: TimelineWeb.UserConfirmationController.create/2
        (timeline 0.1.0) lib/timeline_web/controllers/user_confirmation_controller.ex:1: TimelineWeb.UserConfirmationController.action/2
        (timeline 0.1.0) lib/timeline_web/controllers/user_confirmation_controller.ex:1: TimelineWeb.UserConfirmationController.phoenix_controller_pipeline/2
        (phoenix 1.7.10) lib/phoenix/router.ex:432: Phoenix.Router.__call__/5
        (timeline 0.1.0) lib/timeline_web/endpoint.ex:1: TimelineWeb.Endpoint.plug_builder_call/2
        (timeline 0.1.0) /Users/sergio/Projects/timeline-stack/timeline/deps/plug/lib/plug/debugger.ex:136: TimelineWeb.Endpoint."call (overridable 3)"/2
        (timeline 0.1.0) lib/timeline_web/endpoint.ex:1: TimelineWeb.Endpoint.call/2
        (phoenix 1.7.10) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
        (plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
        (cowboy 2.10.0) /Users/sergio/Projects/timeline-stack/timeline/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.10.0) /Users/sergio/Projects/timeline-stack/timeline/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
        (cowboy 2.10.0) /Users/sergio/Projects/timeline-stack/timeline/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
        (stdlib 5.1.1) proc_lib.erl:241: :proc_lib.init_p_do_apply/3

I looked through the pipeline & other related places, and found no references to the usual flash plugs, so I had no idea why flash was being referenced here at all. Only later on I realized that:

  1. The create function had a verified route for another function in the same controller, which was update
  2. That function did call put_flash/2
def update(conn, %{"token" => token}) do
    case Accounts.confirm_user(token) do
      {:ok, _} ->
        conn
        # |> put_flash(:info, "User confirmed successfully.")
        |> redirect(to: ~p"/")

      :error ->
        # If there is a current user and the account was already confirmed,
        # then odds are that the confirmation link was already visited, either
        # by some automation or by the user themselves, so we redirect without
        # a warning message.
        case conn.assigns do
          %{current_user: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) ->
            redirect(conn, to: ~p"/")

          %{} ->
            conn
            # |> put_flash(:error, "User confirmation link is invalid or it has expired.")
            |> redirect(to: ~p"/")
        end
    end
  end

After removing that reference to put_flash, the error was gone (reasonably).

What I’m wondering is the mechanism that caused that other controller function to emit this error in the first place? I suspect the compiler is doing a check on that verified route, but I thought they only checked the validity of the route, not the plugs called within that route?

It’s behaving as if it did a runtime check of the controller method itself, and I’m unclear on how that works. Why does “put_flash/2” in a verified route seem to be triggered when running that verification?

Thank you! Note that I’ve fixed the error, just curious about how the framework works in this regard.

Showing Posts 1 to 10

sodapopcan

sodapopcan

It’s emitted from the put_flash function, nothing to do with verified routes. See here.

mattei

mattei OP

Understood, but why is that function being triggered, if it’s in a completely different route?

That’s the question at hand: the put_flash function is in a different function in that module, so there is no reason that error should trigger?

beepbeepbopbop

beepbeepbopbop

I would argue that you shouldn’t use flash at all if you’re trying to port phx.gen.auth as a JSON endpoint. Yes, you do need to ensure that the HTTP response headers are and that the cookie is provided, but flash is really something that’s dedicated to inform the user that something happened in one instance.

You could easily also argue that the token you get back shouldn’t be a cookie, but rather part of the JSON response or in the Authorization header in the response

In this instance, I don’t think it’s entirely unreasonable to push the flash responsibility to something else.

mattei

mattei OP

I’m not going to use flash at all! I’m aware it’s not useful and in the process of removing it all, as stated in the post. I’m curious however about the mechanism as to why a flash function (let behind by mistake) in another route would cause an error elsewhere. :slight_smile:

The curious behavior is this: I called route_1 which seemingly has no relation to route_2, but got an error related to a function in route_2. It’s not a compile time error too, rather a runtime one. Why?

LostKobrakai

LostKobrakai

This seems to suggest you update route to be accessed via http. Hard to say what called it though.

sodapopcan

sodapopcan

Ah crap, sorry, I didn’t read your question properly.

Is it the exact same stack trace or you were simply seeing a warning?

adw632

adw632

That was what I noticed too, something is POSTing to the confirmation endpoint route, might it be phx.gen.auth tests?

mattei

mattei OP

Nope–that was me :slight_smile: that’s the endpoint I was hitting to test out. But it’s a separate view from the one the function put_flash was used in.

mattei

mattei OP

That’s the stack trace, yes :slight_smile:

sodapopcan

sodapopcan

But is it the exact stacktrace with line numbers and everything? In a fresh, non-LiveView phx_gen_auth invocation, line 19 of user_confirmation_controller.ex is a call to put_flash which is not present in your first snippet. I assume you removed it, but is it possible you just hadn’t saved the file or something when you originally got the error? Otherwise, put_flash in the update action is line 37.

Just trying to understand because otherwise it makes no sense. There is nothing I can see about verified routes that would rewrite code in such a crazy way that it would cause a runtime exception in a function that isn’t being called.

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

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
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews