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.