I’m writing an application that has a set of resources that require an authorization token to access. Instead of repeating logic in my routes (or worse, potentially forgetting it), I decided to write a plug that would handle this for me. All the routes that need to be authenticated have a :timer_id
parameter in the route, which I then check against the user’s token to ensure they have access to this timer. Here’s a simplified version of my plug
defmodule MyApp.AuthPlug do
def init(opts), do: opts
def call(conn, opts) do
timer_id = timer_id_from_request(conn)
if Auth.token_valid_for_timer(conn, timer_id) do
conn
else
send_resp(conn, 401, "")
|> halt
end
end
defp timer_id_from_request(conn) do
case Map.get(conn.params, "timer_id") do
nil ->
raise "timer id param is not present in connection parameters. This is likely a programming error."
timer_id ->
timer_id
end
end
end
This allows me to ensure that all requests which pass through this plug are properly authenticated, and it’s not possible to accidentally make a route not authenticated by misspelling the route parameter. However, one flaw with this approach is 404 handlers. Now, the following router will throw a 500 on a non-matching route, rather than the 404. This is because when the request passes through the auth plug, it sees there’s no timer_id
parameter on the wildcard route, and it bombs out.
defmodule MyApp.Router do
use Plug.Router
plug(:match)
plug(MyApp.AuthPlug)
plug(:dispatch)
get "/timer/:timer_id" do
# ...
end
match _ do
send_resp(conn, 404, "")
end
end
The way I see it, I have 3 options
- Find some way to detect that I’m in this catch-all case in the plug, and skip the parameter check if so. I attempted to use
Plug.Router.match_path/1
to get the matched path, but I’m not sure exactly how to parse this format to achieve what I want. Looking at its value, I see I get back/my/base/url/*glob/*_path
on an unmatched route, but I don’t know if I can depend on this value being stable. - Omit the defensive check. While this works, I do kind of like that I am prevented from creating an unauthenticated endpoint in an authenticated module by accident.
- Give up on this plug idea entirely.
Is there some way to achieve what I want? Am I going about this in a totally backwards way?