Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ecto and Plug.Conn utilities #11

Closed
rzane opened this issue Jan 16, 2018 · 7 comments
Closed

Ecto and Plug.Conn utilities #11

rzane opened this issue Jan 16, 2018 · 7 comments

Comments

@rzane
Copy link

rzane commented Jan 16, 2018

First, this library is fantastic. Out of the box, the Authority.Template pretty much just works. I really appreciate the approach taken here, because it doesn't get in my way, but also doesn't make me write literally everything from scratch.

Providing a few more conveniences could help make the experience a little smoother, and would prevent the user from having to know security best practices.

Authority.Ecto

I had to look at the project's test suite to see how my schemas should be setup in order to play nice with the template. This is an area where the documentation could probably be improved, but it would be nice to have some convenience changeset functions.

# Hash the password with bcrypt
put_encrypted_password(changeset, :password, :encrypted_password)

# Check that the password meets agreed-upon password strength checks
validate_secure_password(changeset, :password)

# Generate a token, and hash it with HMAC before storing it (should there be an authority function for unhashing before finding the user by token?)
put_secure_token(changeset, :token)

# Set the expires_at on the Token
put_token_expiration(changeset, :token, recovery: {24, :hours}, any: {2, :weeks})

Authority.Plug

It would be nice to have a few functions for working with Plug.Conn. For example:

  • sign_in(conn, user) - put the user in the session
  • sign_out(conn) - clear the user from the session
  • plug Authority.Plug.LoadCurrentUser - lookup the user from the session or from a token in the Authorization header, assign conn.assigns.current_user
  • plug Authority.Plug.Authenticated - ensure that the loaded user is present
  • plug Authority.Plug.Unauthenticated - ensure that the loaded user is not present

I'm interested to get some opinions for how this stuff should be implemented. Should it be part of authority? A separate package? More ideas?

@danielberkompas
Copy link
Contributor

These are great thoughts. I honestly have wondered whether it might be best to move Authority.Template out of the package and into a new authority_ecto package. Seems like it might be a good idea because Authority.Template assumes you're using Ecto.

There could be an authority_plug package, and perhaps an authority_phoenix package as well.

That would be my vote.

@rzane
Copy link
Author

rzane commented Jan 16, 2018

Yeah, it does feel like there should be a separate package for the template, especially since there are a few implicit dependencies (comeonin, bcrypt_elixir).

I'd be happy to work on something like this, and I have some stuff written up that I'm using in my project that I could extract.

@danielberkompas
Copy link
Contributor

I'll put up some new repos for these packages this afternoon and we can collaborate.

@danielberkompas
Copy link
Contributor

@rzane I created https://github.com/infinitered/authority_ecto. Feel free to contribute!

@danielberkompas
Copy link
Contributor

Regarding authority_plug:

We're going in a little different direction with how we use Authority in our projects than what you outlined here.

Instead of using plugs like Authority.Plug.Authenticated to protect functions from unauthorized access, we're moving those checks into the functions themselves. Suppose I had a CMS.list_pages function in my app, which is only available to logged in users.

My Phoenix controller might look like this:

defmodule MyAppWeb.CMSController do
  use MyAppWeb, :controller

  action_fallback MyAppWeb.FallbackController # handles the error case

  def index(conn, _params) do
    with {:ok, pages} <- MyApp.CMS.list_pages(conn.assigns.token) do
      conn
      |> assign(:pages, pages)
      |> render("index.html")
    end
  end
end

Instead of using a plug or passing in a current user, we tokenize the user's credentials when they log in, and store the token in their session. We then pass that token to every function that requires log in. The function can then identify the user and apply access control.

We find this has several benefits:

  • Consistent enforcement. We can have confidence that the permission rules are always applied because the function does it.

  • APIs. We can easily build an API that calls the same functions, because it's all token based. We just give the token to the API client instead of storing it in the session.

  • Clarity. We found that plugs are implicit in a lot of projects. Oftentimes they're applied in the router, not the controller, and it can be really difficult to figure out what permissions are required to call a function when you have a lot of plugs. It's a lot easier when the function implementation itself tells you what permissions are required.

You can see all of this in action in Mithril, our upcoming project generator. I'll give you read access, and you can hit me up in our community Slack for help running and understanding it.

@rzane
Copy link
Author

rzane commented Jan 22, 2018

I haven't published it yet, but I'm working on the plug utilities I mentioned here. It plays quite nicely with authority, but it is completely generic.

@danielberkompas
Copy link
Contributor

With the release of rzane/authenticator on hex, I think we can close this down.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants