Handle 401 error for JSON API

How can I handle a 401 error for my JSON API? Currently I’m handling it like this in my Controller:

 conn
        |> put_resp_content_type("application/json")
        |> send_resp(401, Jason.encode!("Not Authorized"))
        |> halt()

but I would like to just call something like render(conn, "401.json") and have it be handled in the lib/<myapp_web>/views/error_view.ex module as 404 and 500 errors are. I know this is what happens on Ecto.NoResultsError, the function render("404.json", conn) is called in error_view.ex. It looks like I can do it with a custom error from these docs: https://hexdocs.pm/phoenix/errors.html but I’m missing some details on how to do that. I’m not sure if that is the best way to DRY up my code anyways.

Thanks!

I use the fallback controller… and then just let the controller return {:error, :unauthorized } when needed - so the controller is nice and DRY

defmodule Myapp.Web.SomeController do
  ...
  action_fallback(Myapp.Web.FallbackController)


  def show(conn, params) do
   user = SomeAuth.get_user(params)
   if user do
     render normal etc..
   else
     {:error, :unauthorized }
   end 
  end
end

and fallback has:

defmodule Myapp.Web.FallbackController do
  @moduledoc """
  Translates controller action results into valid `Plug.Conn` responses.

  See `Phoenix.Controller.action_fallback/1` for more details.
  """
  use Myapp.Web, :controller

...

  def call(conn, {:error, :unauthorized}) do
    conn
    |> put_status(:unauthorized)
    |> render(Myapp.Web.ErrorView, "auth_required.json")
  end
end

and the ErrorView has a

  def render("auth_required.json", _assigns) do
    %{error: "Unauthorized"}
  end
2 Likes

@outlog that solution is nice, it works. The only issue I have with it is the deprecation warning I get when I run my tests:

Elixir.Phoenix.Controller.render/4 with a view is deprecated, see the documentation for render/3 for an alternative

Ok, I used your solution with a slight modification to the FallbackController in order to prevent the deprecation warning:

defmodule Myapp.Web.FallbackController do
  @moduledoc """
  Translates controller action results into valid `Plug.Conn` responses.

  See `Phoenix.Controller.action_fallback/1` for more details.
  """
  use Myapp.Web, :controller

...

  def call(conn, {:error, :unauthorized}) do
    conn
    |> put_status(:unauthorized)
    |> put_view(Myapp.Web.ErrorView)
    |> render("auth_required.json")
  end
end

Thank you for the help!

1 Like