Junaid Farooq
Junaid Farooq

Reputation: 2608

how to secure a route for authenticated user only Elixir Phoenix

I have created few routes in my application as

  scope "/", Socialistical do
    pipe_through :browser # Use the default browser stack


    get "/", UserController, :index
    get "/sign_up", UserController, :sign_up
    post "/create_user", UserController, :create
    options "/create_user", UserController, :nothing

    post "/session", SessionController, :create
    delete "/logout", SessionController, :delete

    get "/dashboard", DashboardController, :index
  end

As other routes than dashboard are those which could be available to the public. But I want to secure dashboard route for authenticated user only. I have created a session model as

defmodule Socialistical.Session do
  alias Socialistical.User

  def current_user(conn) do
    id = Plug.Conn.get_session(conn, :current_user)
    if id, do: Socialistical.Repo.get(User, id)
  end

  def logged_in?(conn), do: !!current_user(conn)
end

I want to utilise these 2 methods for securing all coming routes which will be available for the authentic user only, Please help me in this. I am kind unaware how could I make/convert it to plug.

Upvotes: 0

Views: 1251

Answers (1)

Steve Pallen
Steve Pallen

Reputation: 4507

Here is a quick example. This examples only handles verifying a logged in user. It does not handle redirecting to a login page.

Here is the plug

defmodule Socialistical.Session do
  @behaviour Plug
  import Plug.Conn
  alias Socialistical.Accounts.User
  alias Socialistical.Repo

  def current_user(conn), do: conn.assigns[:current_user]
  def logged_in?(conn), do: !!current_user(conn)

  def init(opts \\ []) do
    # simple example to show how options can be passed
    %{error: opts[:error] || "Not authorized"}
  end

  def call(conn, opts \\ []) do
    if user = get_user(conn) do
      # we have a session so store it for latter access
      assign conn, :current_user, user
    else
      # not session
      halt_with_error conn, opts[:error]
    end
  end

  defp halt_with_error(conn, error) do
    conn
    |> send_resp(401, error)
    |> halt
  end

  defp get_user(conn) do
    case Plug.Conn.get_session(conn, "current_user") do
      nil -> nil
      id -> Repo.get(User, id)
    end
  end
end

And the router:

defmodule Socialistical.Web.Router do
  use Socialistical.Web, :router
  # ...
  pipeline :protected do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug Socialistical.Session
  end

  scope "/", Socialistical.Web do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/login/:id", PageController, :login

    # just here for the example
    resources "/users", UserController
  end

  scope "/", Socialistical.Web do
    pipe_through :protected
    get "/protected", PageController, :protected
  end
end

Here is a controller for testing. I added a login action just to test it all worked. Its only for demo purposes since it does not authenticate the user, only creates the session.

defmodule Socialistical.Web.PageController do
  use Socialistical.Web, :controller
  alias Socialistical.Accounts

  def index(conn, _params) do
    render conn, "index.html"
  end

  def protected(conn, _params) do
    render conn, "protected.html"
  end

  def login(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    conn
    |> put_session("current_user", user.id)
    |> assign(:current_user, user)
    |> redirect(to: "/")
  end
end

I tested this and it should all work. You should review the Plug docs. As well, you can look at my authentication package Coherence Session plug to get some more ideas.

Upvotes: 1

Related Questions