Phoenix - Unexpected behavior on `render`: render receives Plug.Conn as second parameters

Hi,

I have some experience with Phoenix Liveview but I never had this kind of issue so I hope you can help. I just created a brand new application and I am implementing a Oauth2 auth using ueberauth_facebook

The oauth2 flow is working well, but when I try to return the newly authenticated user and the related JWT, the render function crashes. The log shows that render received a full Plug.Conn%{} struct a second parameter, which doesn’t make any sense.

My routes are setup for JSON (pipe_through :api)

  scope "/auth", PlxWeb do
    pipe_through :api

    get "/:provider/callback", AuthController, :callback
    post "/:provider/callback", AuthController, :callback
    # ...
  end

My controller/handler is quite simple:

  def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
    IO.puts("Callback success")

    case Auth.authenticate_from_callback(auth) do
      {:ok, %{token: _t, user: _u} = auth_info} ->
        IO.puts("user authenticated > #{inspect(auth_info)}")
        conn
        |> put_status(:ok)
        |> render("200.json", %{auth_info})

      {:error, reason} ->
        IO.puts("failed to authenticated user")
        conn
        |> put_status(:internal_server_error)
        |> render("500.json", reason)
    end
  end

And my view and smaller:

  def render("200.json", %{token: _t, user: _u} = payload) do
    IO.puts("Payload > #{inspect(payload)}")
    %{message: "ok", payload: payload}
  end

This code prints the following logs:

> "Callback success"
# means the provider authentication was ok
> "user authenticated > %{token: 'expected token', user: %User{id: xxx, name: 'im a normal user'}}"
# so far all good
> "Payload >  %{conn: %Plug.Conn{adapter: {Plug.Cowboy.Conn, :...}, assigns: %{layout: false, ueberauth_auth: %Ueberauth.Auth{credentials: (+20 lines)"
# why did my token/user map transformed to this?
-> crash: "`protocol Jason.Encoder not implemented for %Plug.Conn{adapter: {Plug.Cowboy.Conn, :...}, assigns: %{kind: :error, layout: false` (+20 lines)"
# which makes sense since the Plug.Conn struct is not expected to be encoded

I don’t have a stacktrace of what’s happening. I’ve been stuck for a couple of hours, I’ve tried difference thing. I don’t even understand how the pattern matching in the view doesn’t fail

Thanks for your kind help, it’s probably some stupid mistakes

This line looks suspect:

|> render("200.json", %{auth_info})

I’d usually expect something like:

|> render("200.json", auth_info: auth_info)

Then in your view:

def render("200.json", %{auth_info: %{token: _t, user: _u} = payload}) do

It’s expected that the conn is added to the assigns between the call to render in your controller and then Phoenix calling your views render function. Take a read of Phoenix.Controller.render to see how it works.

thanks for your reply @mbuhot

I’ve tried different formats and they all have the same results(the one you suggested included, I just tested) - like you suggested I ll take a look at the implementation of render, that might help to understand what’s going on. Thanks !

You have something in your assigns that plug conn cannot convert to html.

Ie you have some sort of embedded struct or module implementation or a method.

Your suth_nfo might be correct object but this line makes things worse.

Just do token: token and test it out.

Well there it is. io.inspect is not the same as render output method. How should render know how to convert Userstruct into html?

I am not sure where you got that piece of code,. It is not readable and open to massive exploits for security reasons.

Thanks all for your reply.

@cenotaph the |> render("200.json", %{auth_info}) line is just one of many things I’ve tried to make it work. Once I got something that work I can clean the code. Also User correctly implement Jason.Encode:

defimpl Jason.Encoder, for: Plx.Auth.User do
  def encode(value, opts) do
    Jason.Encode.map(Map.take(value, [:id, :name, :description]), opts)
  end
end

Anyway, finally I changed some code for something even more basic and did that:

# Controller
      {:ok, _} ->
        conn
        |> put_status(:ok)
        |> render("200.json", %{hello: "world"})

# View
  def render("200.json", info) do
    IO.puts("info > #{inspect(info)}")
    %{data: info}
  end

And it still fails with the same error:

protocol Jason.Encoder not implemented for %Plug.Conn{adapter: {Plug.Cowboy.Conn, :...}, assigns: %{kind: :error, layout: false, reason: 

I will just create a brand new app and try again. I must have made an error in the configs

I think the problem is you’re treating render as you would the helper json that indeed takes a map or any json encodable element as the last and renders it as json.

In this case I think you will have to do it as a regular assign, such as render(conn, "vie.json", auth_info: auth_info) and then pick %{auth_info: auth_info} from the parameters on the json render.

I got it, I simply forgot import Phoenix.Controller at the top of my controller… sorry and thanks to all for your help.

1 Like