Coherence (Devise alternative)
Finally, I found this project that's been under heavy development for the past 6 months called Coherence. For all intents and purposes, it successfully mimics Devise in almost every way. And this is a very good thing for a lot of scenarios.
Their README is well explained enough so I will not copy and paste it here, just read it there to get up and running. But if you want to try out all of their features you can tweak their procedure with this set of options in the installation Mix task:
1 |
mix coherence.install --full --rememberable --invitable --trackable |
Run mix help coherence.install
to see a description for all the options.
And if you're not tweaking the front-end, you can just add the proper sign up, sign in, sign out links by adding the following snippet to the web/templates/layout/app.html.eex
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<header class="header"> <nav role="navigation"> <ul class="nav nav-pills pull-right"> <%= if Coherence.current_user(@conn) do %> <%= if @conn.assigns[:remembered] do %> <li style="color: red;">!!</li> <% end %> <% end %> <%= YourApp.Coherence.ViewHelpers.coherence_links(@conn, :layout) %> <li><a href="https://www.phoenixframework.org/docs">Get Started</a></li> </ul> </nav> <span class="logo"></span> </header> ... |
(By the way, whenever you see YourApp
in the code snippets, you must change for your app's module name.)
If you get lost in their documentation you can check out their Coherence Demo repository for an example of a basic Phoenix app with Coherence already configured and working. You will mostly have to take care of web/router.ex
to create a :protected
pipeline and set the scopes accordingly.
If you do it correctly, this is what you will see:
It's been a long while since I got excited by a simple sign in page!
Ex Admin (ActiveAdmin alternative)
Then, the next step I usually like to do is to add a simple Administration interface. To that end I found the Ex Admin, that's been under heavy development since at least May of 2015. It's so damn close to ActiveAdmin that it's old theme will make you forget you're not in a Rails application.
Again, it's pretty straightforward to set it up by just following their README instructions.
Once you have it installed and configure, you can very quickly expose the User model into the Admin interface like this:
1 |
mix admin.gen.resource User |
And we can edit the web/admin/user.ex
with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
defmodule YourApp.ExAdmin.User do use ExAdmin.Register register_resource YourApp.User do index do selectable_column column :id column :name column :email column :last_sign_in_at column :last_sign_in_ip column :sign_in_count end show _user do attributes_table do row :id row :name row :email row :reset_password_token row :reset_password_sent_at row :locked_at row :unlock_token row :sign_in_count row :current_sign_in_at row :last_sign_in_at row :current_sign_in_ip row :last_sign_in_ip end end form user do inputs do input user, :name input user, :email input user, :password, type: :password input user, :password_confirmation, type: :password end end end end |
Yes, this is eerily similar to the ActiveAdmin DSL. Thumbs up for the team responsible, and it really shows how Elixir is well suited for Domain Specific Languages, if you're into that.
If you followed the Coherence instructions, it asks you to add a :protected
pipeline (a set of plugs) for your protected routes. For now you can add the /admin
route to go through that pipeline. And for the uninitiated, a "plug" is similar in concept to a Rack app, or more specifically, a Rails middleware. But in Rails we only have one pipeline of middlewares. In Phoenix we can configure multiple pipelines for different set of routes (browser and api, for example).
So we can add the following to web/router.ex
:
1 2 3 4 5 6 |
... scope "/admin", ExAdmin do pipe_through :protected admin_routes end ... |
With those simple contraptions in place, you will end up with something like this:
And if you're still not conviced, how about changing to their old theme?
Hell yeah! Makes me feel right at home, although I really prefer the new theme. But you could replace your ActiveAdmin-based app for this one and your users would hardly notice the small differences in the interface. The behavior is basically the same.
If you still have questions on how to properly configure ExAdmin, check out their Contact Demo project, where you can find a real example.
Stitching a simple Admin role
Obviously, we don't want to let all authenticated user to access the Admin section.
So we can add a simple boolean field in the users table to indicate whether a user is an admin or not. You can change your migration to resemble this:
1 2 3 4 5 6 7 8 9 10 11 12 |
... def change do create table(:users, primary_key: false) do add :name, :string add :email, :string ... add :admin, :boolean, default: false ... end end ... |
And you can configure the priv/repos/seeds.exs
file to create 2 users, one admin and one guest:
1 2 3 4 5 6 7 |
YourApp.Repo.delete_all YourApp.User YourApp.User.changeset(%YourApp.User{}, %{name: "Administrator", email: "admin@example.org", password: "password", password_confirmation: "password", admin: true}) |> YourApp.Repo.insert! YourApp.User.changeset(%YourApp.User{}, %{name: "Guest", email: "guest@example.org", password: "password", password_confirmation: "password", admin: false}) |> YourApp.Repo.insert! |
As this is just an exercise, you can drop the database and recreate it, like this: mix do ecto.drop, ecto.setup
.
Coherence takes care of authentication, but we need to take care of authorization. You will find many examples online to something that resembles Rails' Pundit, such as Bodyguard. But for this post I will stick to a simple Plug and create a new Router pipeline.
We need to create lib/your_app/plugs/authorized.ex
and add the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
defmodule YourApp.Plugs.Authorized do @behaviour Plug import Plug.Conn import Phoenix.Controller def init(default), do: default def call(%{assigns: %{current_user: current_user}} = conn, _) do if current_user.admin do conn else conn |> flash_and_redirect end end def call(conn, _) do conn |> flash_and_redirect end defp flash_and_redirect(conn) do conn |> put_flash(:error, "You do not have the proper authorization to do that") |> redirect(to: "/") |> halt end end |
Once a user signs in, Coherence puts the structure of the authenticated user into the conn
(a Plug.Conn structure), so we can pattern match from it.
Now we need to create the router pipeline in the web/router.ex
like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
... pipeline :protected_admin do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug Coherence.Authentication.Session, protected: true plug YourApp.Plugs.Authorized end ... scope "/" do pipe_through :protected_admin coherence_routes :protected_admin end ... scope "/admin", ExAdmin do pipe_through :protected_admin admin_routes end ... |
The :protected_admin
pipeline is exactly the same as :protected
but we add the newly created YourApp.Plugs.Authorized
plug at the end. And then we change the /admin
scope to go through this new pipeline.
And that's it. If you log in with the guest@example.org
user, it will be kicked out to the homepage with a message saying that it's not authorized. If you log in with the admin@example.org
it will be able to access the ExAdmin interface in /admin
.
Wrapping Up
Even though it's now super simple to add Authentication, Administration and basic Authorization, don't be fooled, the learning curve is still steep, even if you've been a Rails developer for a while.
Because of what's underneath, the OTP architecture, the concepts of Applications, Supervisors, Workers, etc, it's not immediatelly simple to wrap your head around what's really going on. If you're not careful, libraries such as Coherence or ExAdmin will make you feel like it's just as simple as Rails.
And it's not like that. Elixir is a completely different beast. And I mean it in a bad way, on the contrary. It's meant for highly reliable and distributed systems and it demands way more knowledge, patience and training from the programmer.
On the other hand, exactly because libraries such as Coherence makes it a lot easier to get started, you may become more motivated to put something up and running and then investing more time really understanding what's going on underneath. So the recommendation is: get your hands dirty, get some quick instant gratification of seeing something running, and then go on and refine your knowledge. It will be way more rewarding if you do so.
I don't see Phoenix meant to just be a Rails replacement. This would be too easy. I see it more as another piece to make Elixir the best suited set of technologies to build highly scalable, highly reliable, highly distributed systems. Stopping at simple web applications would not fulfill Elixir's potential.