Pow: Robust, modular, extendable user authentication and management system

It’s a great project! I used it on my golf scorecard app which has a web interface and a graphql api for the companion React Native golf mobile app.

I plan on converting another existing app from Coherence soon and will send you suggested updates as I go through the docs.

6 Likes

Thanks! Here’s a guide for migrating from Coherence to Pow: Migrating from Coherence — Pow v1.0.1

Also, in case anybody is interested, there was a discussion on how Pow could be used with GraphQL endpoints that highlights how useful the modular setup is for when you need a custom flow: Use Pow.PasswordReset.Plug.create_reset_token without a conn · Issue #61 · pow-auth/pow · GitHub

5 Likes

There’s been a few questions on Github on how to use Pow on read-only platforms like Heroku. For production run the ETS cache is not recommended, and the Mnesia cache requires write access.

So I’ve written a guide on how to use Redis instead: https://github.com/danschultzer/pow/blob/master/guides/REDIS_CACHE_STORE_BACKEND.md

You are not limited to Redis as an alternative. It’s super easy to switch to whichever key-value store you prefer or have access to :smile:

5 Likes

I’ve been working on an invitation extension that provides a basic platform for this: PowInvitation extension by danschultzer · Pull Request #113 · pow-auth/pow · GitHub

You can check out the readme for to see how the extension can be used: https://github.com/danschultzer/pow/blob/pow-invitation/lib/extensions/invitation/README.md

It should cover most or all your requirements :smile:

You can try it out by pulling the pow-invitation branch from github. It’s still WIP but it works. As always, feedback is most welcome and will help a lot!

2 Likes

Pow 1.0.3 released

Changelog: Github
Hex: https://hex.pm/packages/pow/1.0.3

PowInvitation

Now it is super easy to add an invitation system to your platform with Pow! Check out the documentation to see how easy it is to configure it for your exact needs: https://github.com/danschultzer/pow/blob/v1.0.3/lib/extensions/invitation/README.md

This has been a highly requested feature, and I’m happy that we finally got a flexible solution in Pow.

What’s next?

WebAuthn

WebAuthn standard has been finalized! I believe Pow should have built-in 2FA/password-less authentication support, and this is something I hope to look at soon. I would be grateful for any input.

There’s an issue for it here: https://github.com/danschultzer/pow/issues/6

Maintainers

So far this has been a self-funded project, and I’ve been fortunate enough to have enough time to work full time on this. However I’ll probably get back to my startup life soon, so I’ve worked hard to streamline the codebase, and made it easy for others to join in.

The best way to help out is to open PR’s or helping with issues. When you become familiar with how Pow works you will be invited to join as maintainer. Thanks!

Security audit

Pow definitely needs some eyes on security practices. @griffinbyatt is one of the few people I’ve seen doing this. If there’s any person or company that’s willing to put some time into this, I would be deeply grateful.

Happy coding :rocket:

19 Likes

You are a power-house, mate. :049: Thank you for the hard work!

7 Likes

You might want to contact Radically Open Security. Testing the security of an open source auth system seems like it would be right up their alley.

https://radicallyopensecurity.com/

7 Likes

@danschultzer Can you give us a quick iex snippet on how to programmatically change a user’s password?

Additionally, some of us could really use a separate umbrella step-by-step tutorial. Right now I am not even sure if some parts of the config belong to the Ecto app or the Phoenix app. :frowning:

As an example: when I execute mix pow.phoenix.gen.templates it keeps telling me to add the web_module: ... snippet to my config even though both my apps have that in their config. So it quickly gets confusing for umbrella users.

There’re multiple ways, and it depends on the usage. If you don’t care about validating anything but the password, then just run:

User.pow_password_changeset(user, %{"password" => password, "confirm_password" => password})

I think this confusion can be eliminated by just having better mix helper messages, e.g. ignore :web_module instructions if the :web_module has already been set in config. This would probably go most of the way. You would only need to put the config in the Phoenix app.

2 Likes

Thank you. Mind if I contact you in private for a few more questions?

Sure, go ahead!

So I played a bit with Pow and I can say I am very pleased with its modules and their docs.

Please do you think adding a feature such as disabling users so when the disabled users try to sign in they get a relevant message like “Sorry your account is disabled”. Or maybe this is not a too common feature ?

For now I extendend the users_context module to check if the the authenticating user is disabled and return nil in the authenticate/1 method.
Unfortunately doing that makes both a user entering wrong credentials and a disabled one entering right credentials, get the same error message.
So if I understand well the docs, I will have to create a custom session controller and edit web_app/router.ex. And also provide custom authorization + error handler plugs.
I just want to be sure that this the good way for doing things. But if you’re planing to add such a feature or to make its implementation even easier, it will be of course also fine! :smile:

1 Like

You are the second dev to ask for it in two days, so there’s definitely good interest for it. However, I think it’s so trivial that it’s better to let the dev take charge and modify the flow. It helps ensure that developers also adds proper tests to their flow :smile:

It’s best to handle it at the plug level. The context module is responsible for interactions with the database, and I prefer that they have very limited logic.

I would add a plug module like this to handle locked accounts:

defmodule MyAppWeb.EnsureUserNotLockedPlug do
  @moduledoc """
  This plug ensures that a user isn't locked.

  ## Example

      plug MyAppWeb.EnsureUserNotLockedPlug
  """
  alias MyAppWeb.Router.Helpers, as: Routes
  alias Phoenix.Controller
  alias Plug.Conn
  alias Pow.Plug

  @doc false
  @spec init(any()) :: any()
  def init(opts), do: opts

  @doc false
  @spec call(Conn.t(), any()) :: Conn.t()
  def call(conn, _opts) do
    conn
    |> Plug.current_user()
    |> locked?()
    |> maybe_halt(conn)
  end

  defp locked?(%{locked_at: locked_at}) when not is_nil(locked_at), do: true
  defp locked?(_user), do: false

  defp maybe_halt(true, conn) do
    conn
    |> Plug.clear_authenticated_user()
    |> Controller.put_flash(:error, "Sorry, your account is locked.")
    |> Controller.redirect(to: Routes.pow_session_path(conn, :new))
  end
  defp maybe_halt(_any, conn), do: conn
end

And then add the plug to your endpoint OR router depending what works better for your flow:

defmodule MyAppWeb.Endpoint do
  # ...

  plug Pow.Plug.Session, otp_app: :my_app_web

  plug MyAppWeb.EnsureUserNotLockedPlug

  # ...
end
defmodule MyAppWeb.Router do
  # ...

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug MyAppWeb.EnsureUserNotLockedPlug
  end

  # ...
end

I think it would be good to have as a guide, similar to the user role guide: https://github.com/danschultzer/pow/blob/master/guides/USER_ROLES.md

5 Likes

Isn’t the is_ prefix and the ? postfix redundent? Only one or the other is the pattern (is_ is more common than ? I notice).

is_ is usually only used for guard-safe predicates, while ? is used for predicates that can not be used in guards.

1 Like

Yeah, it’s a typo, I meant defp locked?, but couldn’t update it because discourse doesn’t allow to fix typos anymore (just keep getting “too few changes to the body” error, maybe something you want to look at @AstonJ?).

1 Like

Might be worth making the changes, copying the entire post - then typing anything in there and clicking save, then edit again and pasting what you actually wanted in there :slight_smile:

Edit: Should be fixed now :slight_smile:

1 Like

I’ve created a quick PR for a guide on this: https://github.com/danschultzer/pow/blob/locked-users/guides/LOCK_USERS.md

@Kurisu and @dimitarvp (and anybody who’s interested in this) - comments would be super helpful!

4 Likes

Great that is fine for me. A Plug is really the right way to go. ^^

My other concern is about the reset_password extension. I think if an user account is locked, sending an email for password reset will be pointless. Any advice about how to prevent such a behavior ? I guess this time we should handle it in a custom reset_password controller.