Interesting Router Quirk

I have code to steer a user away from a URL that they shouldn’t have access to (my ‘protector’ code).

My code was using redirect(to: "/") which turned out to be unreliable (the user remained on the forbidden target page instead of redirecting the user back to the home page). Sometimes it seemed to work, other times it didn’t!?

Apparently, when the docs say that the to: parameter is a ‘relative URL’, they really mean it! It seems to be relative to the current URL instead of the website root URL.

Example: if the target URL is http://mysite.com/dangerous_data/5/edit, then the relative URL of “/” just redirects the user to http://mysite.com/dangerous_data/5/edit/ instead of http://mysite.com/.

To get the user back to the home page, I had to add a route that specified get("/index", PageController, :index) and use redirect(to: "/index") in my ‘website protector’ code.

My guess is you have a plug for the auth check where you neglected to call halt/1 after the call to redirect, which would allow the plug pipeline to continue down to the controller action. The following should work as you want:


def my_auth_plug(conn, _) do
  if ... do
    conn
  else
    conn
    |> redirect(to: "/")
    |> halt()
  end
end
5 Likes

Thanks for the tip!

I wasn’t using a plug but your solution looks clean and a better idea than what I’m using.

1 Like

For posterity, can you share your previous code? It’s also sounds like you may have returned the wrong conn binding so your redirect call had no effect in the controller.

5 Likes

Sure! BTW: It looks like I was using a plug!

defmodule PtrackWeb.Plugs.SetUser do
  import Plug.Conn

  def init(_params) do
  end

  def call(conn, _params) do
    PtrackWeb.AuthController.check_access(conn)
    # Do other things
  end
end

defmodule PtrackWeb.AuthController do
  use PtrackWeb, :controller

  def isLocal(conn) do
    host = List.first(get_req_header(conn, "host"))
    host =~ "localhost" || host =~ "127.0.0.1"
  end

  def check_access(conn) do
    # Check if they are trying to access a page that can muck up the data
    # If they are and they are not running on localhost, interrupt their mischief!
    unsafe = Enum.member?(["upload", "edit", "update", "delete", "new", "create"],
      String.split(conn.request_path, "/")
      |> List.last()
    )

    if(unsafe && !isLocal(conn)) do
      conn
      |> put_flash(:danger, "Hackers need not apply!")
      |> redirect(to: "/")
    end
  end
end

defmodule PtrackWeb.Router do
  use PtrackWeb, :router

  pipeline :browser do
    plug(:accepts, ["html"])
    plug(:fetch_session)
    plug(:fetch_flash)
    plug(:protect_from_forgery)
    plug(:put_secure_browser_headers)
    plug(PtrackWeb.Plugs.SetUser) #checks that the page is safe for them to fetch
  end

  pipeline :auth do
    plug(PtrackWeb.Plugs.RequireAuth)
  end

  scope "/", PtrackWeb do
    pipe_through([:browser, :auth])

  # Typical router code like:
    resources("/tickets", TicketController)
  # etc...
  end
end

I think that’s all of the relevant code.

I was testing the URL “http://thesite.com/tickets/682/edit” so I should have been kicked back to my homepage, which it did, according to the terminal’s debug info, but the edit form remained on screen instead of displaying the home page!?!

Sending the user back to “/index” instead of “/” is what got my system to display the homepage after failing the check_access() test.

The path in this case isn’t the issue. What’s happening is the SetUser plug fails tohalt the conn, because your PtrackWeb.AuthController.check_access/1 plug redirects, but doesn’t halt. It may be confusing because you’ll see controllers redirect but not call halt. Controllers are the end of the line as far as the plug pipeline goes, so there is nothing downstream to halt from, but when you redirect (or send a response) upstream of the controller via a plug, you must halt to prevent downstream plugs from being called. I think the “/index” vs “/” is a fluke in this case.

2 Likes

Thanks! Now I understand what you are saying after I carefully re-read your comment.