User authentication in Phoenix?

Can you do a blog post (or even forum post) as a walk-through please Hubert? I think that would be a huge help for those wishing to roll their own :slight_smile:

2 Likes

Does anyone know of a blog post/tutorial showing how to setup, remember me functionality as well as, resetting and confirming passwords when using guardian?

5 Likes

When looking on Hex or on the awesome-elixir list, it becomes clear that there are many different use authentication and management packagges for Phoenix.

All of them do different things, and all of them are in different states of completion.

Yesterday I burned my fingers on Addict. It claims to manage many things for you. I was however unable to use it for my project:

  • I could not find out how to log a user in for integration(/controller) tests.
  • What happens when a user gets registered or logged in is hard-coded in Javascript
  • It is, at least by default, only possible to register/log users in using AJAX; a simple log-out link will thus not work.

I have read about Guardian, which is said to (I havenā€™t used it myself yet) do authentication very well (but all user-management on top of that needs to be provided by you).

Then there is Ueberauth with many different adapters, which seems to be somehow connected to Guardian.

There also is sentinel that adds some functionality on top of Guardian.

We also have Aceaus, Blackbook and many othersā€¦


What authentication/user management packages have you used? What were your experiences with them? Which ones would you recommend, and which ones are definitely unfinished right now?

6 Likes

I would love to see an auth system built in to Phoenix by default (anyone not wanting auth can simply specify as such on app creation).

There are several advantages to this:

  • Auth system/code is generated with the app and so is easily viewable/customisable
  • Unified system means libraries can take advantage of if

The Volt framework has built in auth and itā€™s something many people commented as being one of its smart choices. Auth is one of the first things most apps will need - proven by the popularity of libraries such as Devise - having a single smart system shipped by default makes a lot of sense to me.

9 Likes

I agree.

On the other hand, of course, is the fact that exactly how authentication should work varies greatly from application to application.

I believe that by generating this code, it would be a lot simpler to add your own flavour to it, than it would be when plugging into a system that mostly runs somewhere ā€˜behind the scenesā€™.

2 Likes

I think there could be two levels offered - basic and full(er) :slight_smile:

1 Like

The rationale against this is that even the smallest authentication system is still orthogonal to the rest of phoenix. All of these authentication systems are just plugs, no explicit support in phoenix is required at all.

The modular nature of phoenix means that even if there were a phoenix core supported auth solution it would still be a separate dependency, and then all weā€™ve achieved is adding additional code the phoenix core team is now responsible for.

8 Likes

Iā€™ve tried Addict and Openmaize in a Phoenix application.

I didnā€™t like Addict too much because it relies heavily on JavaScript (for example, on the redirections).

Instead, I chose Openmaize ā€“ itā€™s very well documented, doesnā€™t rely on JS, and provides signup/login/logout/password reset features out of the box. Ah, and two-factor authentication as well.

12 Likes

Coherence is a new library by @smpallen99: https://github.com/smpallen99/coherence

Note: This is an early release, under active development. But it looks very promising so far.

9 Likes

I would love to get feedback on Coherence. If you give it a try, let me know how it works for you. And more importantly, if it doesnā€™t, Iā€™ like to know why.

Steve

6 Likes

Hello! It seems to be very promising. It is the best authentication package, it follows the ā€œconvention over configurationā€. It should only enlarge.

How can I make a user only modify their own data?

1 Like

@claudiojulio Thanks for the feedback. Controlling who can access what resource is the role of authorization. Coherence is a user management and authorization solution. You will need an authorization package like canary for this.

I will soon add a something about using canary with coherence to either the wiki or the readme.

Steve

4 Likes

Coherence seems great :+1:

1 Like

I use ueberauth/guardian. Specifically ueberauth for LDAP (custom module) and GMail login. Guardian for API JWT tokens, and phoenix session tokens for normal user interface (because it has to hit the DB/Cache to access the significantly more detailed permission set on normal access where-as ā€˜mostā€™ API calls do not need to via the JWT token).

Presence is actually quite nice as it is, I do use it to watch ā€˜Usersā€™ in some, but I also have some keyā€™d on server-to-server connections and more as well. I would not want it tied to a User format as it already supports that perfectly as it is, while also supporting a generic use-case. :slight_smile:

I made a detailed generic permission system that works seemlessly wonderfully with canary: https://hex.pm/packages/permission_ex

I have a database that feeds a time-based cache for holding the permission structures. But I can define my permissions like:


defmodule MyServer.Perms.Help do
  @behaviour MyServer.PermsBehaviour
  defstruct action: nil, uid: :_

  def get_as_new(), do: %MyServer.Perms.Help{action: "", uid: :_}
end


defmodule MyServer.Perms.Help.Issue do
  @behaviour MyServer.PermsBehaviour
  defstruct action: nil, id: :_

  def get_as_new(), do: %MyServer.Perms.Help.Issue{action: "", id: :_}
end

That with my Canada conn handler will grab from the cache/db so I can do things (via the Happy module as well) like:

  def delete_tag(conn, %{"id" => id_param, "tag" => tag_param}) do
    happy_path!(else: handle_error(conn)) do
      {id, ""} = Integer.parse(id_param)
      @perm true = conn |> can?(delete_tag(%Perms.Help.Issue{id: id}))
      @param {:ok, tag_id} when is_integer(tag_id) = case Integer.parse(tag_param) do
        {tag_id, ""} -> {:ok, tag_id}
        _ -> {:ok, Repo.one!(query_tag_by_name(tag_param)).name}
      end
      Repo.delete_all(from it in Issue.Tag, where: it.issue_id == ^id and it.tag_id == ^tag_id)
      conn
      |> put_flash(:info, gettext("Successfully removed tag"))
      |> redirect(to: help_issue_path(conn, :show, id))
    end
  end

  def edit(conn, %{"id" => id}) do
    happy_path!(else: handle_error(conn)) do
      @perm true = conn |> can?(edit(%Perms.Help.Issue{}))
      issue = Repo.get!(Issue, id)
      @perm true = conn |> can?(edit(%Perms.Help.Issue{id: issue.id}))
      changeset = Issue.changeset(issue)
      render(conn, :edit, issue: issue, changeset: changeset)
    end
  end

Or other things like that. Basically canadaā€™s :action is autofilled into a permission if an action field exists on it and action is not null from canada, and any other arguments are as passed in to the permission structure. This is then run through my cache to get the permission of the user and then tested. My conn handler (I have others too) is:

defimpl Canada.Can, for: Plug.Conn do # Some stuff stripped for brevity

  alias MyServer.PermissionHelper

  def can?(conn, nil, req) do
    user = UserHelper.get_from_conn(conn)
    is_user_authorized?(req, user)
  end
  def can?(conn, action, %{action: _old_action} = req) do
    user = UserHelper.get_from_conn(conn)
    is_user_authorized?(%{req | action: action}, user)
  end

  defp is_user_authorized?(required, nil) do
    is_perms_authorized?(required, PermissionHelper.get_permissions(nil))
  end
  defp is_user_authorized?(required, %MyServer.LDAPUser{uid: uid}) do
    # TODO:  The `required` var might be a list do remember!!!  Not used yet, but someday...
    is_perms_authorized?(required, PermissionHelper.get_permissions(uid))
  end

  defp is_perms_authorized?([required], perms) do
    is_perms_authorized?(required, perms)
  end
  defp is_perms_authorized?({:any, required}, perms) do
    required
    |> Enum.any?(&is_perms_authorized?(&1, perms))
  end
  defp is_perms_authorized?({:all, required}, perms) do
    required
    |> Enum.all?(&is_perms_authorized?(&1, perms))
  end
  defp is_perms_authorized?(required, perms) do
    PermissionEx.test_tagged_permissions(required, perms)
  end

And of course if anyone sees bugs please tell me. :slight_smile:

9 Likes

I just pushed the CoherenceDemo canary branch to show an example of how to add Authorization to a project using Coherence.

4 Likes

Thanks for contributing man!

I am new to phoenix and I am looking at user authentication and thereā€™s nothing like Devise here until I found Coherence. :slight_smile:

Most of the tutorials found online are"roll-your-own" authentication methods using comeonin or using ueberauthā€¦

1 Like

Thanks for the detail example. I began my project using Ueberauth/Guardian. But after reading code and examples, I decided I am just not smart enough yet to understand what I need to do to implement a full authentication solution using it.

Looking at your Coherence & Canary demo, I feel initially more confident. However, I could not determine if Coherence supports authentication mechanism for Phoenix Channels.

I need to ensure socket messages identify an authenticated user. I believe I can use Canary to authorize the resulting action from the socket message. But how can Coherence be used to pass a token in the message? Or is this an inappropriate usage scenario?

I have to bitch a little about this too, doing user auth. with phoenix(oauth) for the first time was painful to say the leastā€¦ It took me about 10x as long as first time with django or x js framework and I am still not sure about the damn thing :slight_smile:

Also I could find just 1 example including reactā€¦

2 Likes

I believe itā€™s possible to use Ueberauth with Coherence through ueberauth_identity package. From there, you can still use guardian and what not. Anyway, Iā€™m about to try just that.

1 Like

Is it just me or is using Ueberauth + Guardian really complicated? Or it could be that there arenā€™t enough resources that teaches you how to use them. I couldnā€™t get them to work by just reading the documentations. Phoenix Guardian Demo Application led me to the right direction but it is a lot to digest without any blog posts to compliment it. Here is what I needed to get them to work together:

 28 files changed, 678 insertions(+), 35 deletions(-)
 create mode 100644 priv/repo/migrations/20160910210757_create_user.exs
 create mode 100644 priv/repo/migrations/20160910214502_create_authorization.exs
 create mode 100644 priv/repo/migrations/20160910223402_create_guardian_db.exs
 create mode 100644 web/auth/guardian_serializer.ex
 create mode 100644 web/auth/user_from_auth.ex
 create mode 100644 web/controllers/auth_controller.ex
 create mode 100644 web/controllers/helpers.ex
 create mode 100644 web/controllers/signup_controller.ex
 create mode 100644 web/models/authorization.ex
 create mode 100644 web/models/guardian_token.ex
 create mode 100644 web/models/user.ex
 create mode 100644 web/models/user_from_auth.ex
 create mode 100644 web/templates/auth/login.html.eex
 create mode 100644 web/templates/layout/login_bar.html.eex
 create mode 100644 web/templates/signup/new.html.eex
 create mode 100644 web/views/auth_view.ex
 create mode 100644 web/views/helpers.ex
 create mode 100644 web/views/signup_view.ex

I believe a lot of the implementation can be put into the libraries with the option to have custom implementations or the approach Coherence seems to be taking by using generators.

5 Likes