User authentication in Phoenix?

Authentication in Phoenix/Elixir app with Ueberauth and Guardian

12 Likes

Addict and Guardian are both great. However, exactly like with Rails, there is built in solution into the framework that does most of the heavy lifting for you. Look at Phoenix.Token and Comeonin.Bcrypt combination to generate signed token (that you set in a cookie) and hash/compare passwords respectively.

4 Likes

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