Similar to Ruby’s Rack, Plug is a general specification for composing modules between web applications and application servers. Here’s how we can use them to build authorized pipelines in your router.
Note that this post is not about whether you should do authorization at the router level. It’s likely you’ll do it as part of your business logic for the most part. But when it makes sense, you can use Plugs.
Authorization plug is a generic plug, but one that might need to stop the execution of the request. If we import Plug.Conn
we can pipe to halt/1
to do exactly that. We should also render a custom error message.
I’ll demonstrate by checking claims from a Guardian token and compare them to fix hardcoded access rights.
defmodule DigiWeb.VerifyAdminPlug do
import Plug.Conn
def init(options), do: options
# This is what's get called
def call(%Plug.Conn{} = conn, opts) do
verify_access!(conn, opts)
end
defp verify_access!(conn, opts) do
# This is a path like "/admin/path1"
authorized_path = conn.request_path
# We fetch the authorization rights from claims
rights =
if Map.has_key?(conn.private, :guardian_default_claims) do
conn.private.guardian_default_claims["rights"]
else
[]
end
# A simple way of checking a path against a particular right,
# could be a more sophisticated check
required_rights = required_action_rights[authorized_path]
if required_rights do
case has_rights(rights, required_rights) do
false ->
conn
|> auth_error()
|> halt()
true ->
conn
end
else
conn
|> auth_error()
|> halt()
end
end
defp has_rights([], _required), do: false
defp has_rights(rights, required) do
MapSet.subset?(MapSet.new(rights), MapSet.new(required))
end
defp auth_error(conn) do
body = Poison.encode!(%{message: "Unauthorized"})
send_resp(conn, 401, body)
end
def required_action_rights do
%{
"/admin/path1" => ["right1"],
"/admin/path2" => ["right2"]
}
end
end
There you have it, a simple way to authorize access. Great for pages that are protected entirely.
To make a route use your plug, plug it in a pipeline:
# router.ex
..
pipeline :authorized_admin do
plug Guardian.Plug.Pipeline,
module: MyAppWeb.Public.Guardian,
error_handler: MyAppWeb.Public.ErrorHandler
plug DigiWeb.Public.GraphiQlAuthentication
# Check admin's access rights
plug MyApp.VerifyAdminPlug
end
..
Because I wanted to use claims from a token issued by Guardian, I put it after the Guardian’s plug.