Auth framework: need advice based on a list of prerequisites

authentication
phoenix
coherence
pow

#1

Hey everybody! :039:
I am looking for the auth framework that can help me with a project, given these specifics:

  1. There are no public sections or pages. Non-authenticated users can only land on the home page and they cannot go anywhere else before signing in.
  2. You cannot just sign up. An admin has to invite you first and you get a link in your email inbox that allows you to then sign up. The classic sign up model is either out of the question, or as a compromise it can be implemented only if the new account has to be approved by an admin before being able to sign in.
  3. As per above: there should be admins and users and admins can approve user accounts.
  4. The framework should allow for more roles than admin and user in the future – but I am guessing I have to study how can an authorization library work with the chosen framework or homegrown solution instead.
  5. The framework should in general be extensible in a non-bloated way, meaning if I want to customize something it does I shouldn’t have to copy several files 200+ lines long and then apply 3-line diffs to them. I know this is a very tough thing to do, it’s just that I would gladly take it if it was out there.

I looked at both Coherence and Pow. Also entertained the idea of Guardian with my own code on top.


Notes about Coherence:

  • Coherence seems to carry a lot of file baggage with it and integrates very tightly with myapp_web which I don’t like and I don’t have the time to untangle properly into a separate auth app inside an umbrella. I definitely could do it in theory but I have no guarantee that it’s only an 1-2 hour job which is scaring me and thus I haven’t tried that yet.
  • I suffered a problem with it caching the user model so aggressively that a linked belongs_to object was never really invalidated and reloaded and was thus showing wrong data on the pages. I was forced to instruct Coherence to copy its controllers inside the project and to then overwrite how they load the user model. Result: no caching at all, every page incurs a reload of the user itself and its linked object. Basically defeated the really neat idea that the Coherence author had. :frowning: This is definitely my bad and not of Coherence! But in the 10 minutes I tried to find a solution, I failed.
  • It does indeed support invitations but I admit I haven’t tried to disable normal sign up and gate it behind an invitation only. So I am not sure if my desired use-case is supported out of the box, if it’s possible with a small effort, or would be a hassle to achieve (but still possible).

/CC @smpallen99, the author of Coherence.


Notes about Pow:

  • A bit smaller than Coherence but basically has the same problem of file baggage and no umbrella-friendly installer (namely no option to generate an entirely separate app inside an umbrella). I really don’t know why library/framework authors don’t make this a first-class citizen in their mix tasks, it would be very highly appreciated!
  • Didn’t seem to capture my use case about being able to invite users and not allow them to sign in before approved by an admin. I liked the author’s response when I asked him – what he proposed sounded easy enough – it’s just that in a startup scenario you really cannot get distracted with customizing frameworks when you are building an MVP. You either have to use ready-baked solutions or know exactly what you are doing when customizing (and thus still do it very quickly). Every hour counts in these conditions.

/CC @danschultzer, the author of Pow.


Admittedly I am not an expert in both of these and sadly the time is really not right for me to indulge in weeks-long toy projects to inform myself properly.

Finally, I looked into Guardian because I used it before. It’s really easy and minimastlic to start with and is currently the decision I lean to (in combination with comeonin and my own DB model for invitations). Not really sure if it wouldn’t quickly get harder than Coherence and Pow though.

Don’t get me wrong. I am not lazy. I am open to devote to one framework and then become an expert – subsequently it’s likely I would be contributing to it as well. But right now I am really pressed with time and I am looking for a practical advice before pulling the trigger.

Any advice and battle stories are appreciated. Thanks for your consideration! :023:


Elixir Forum 2019 Update!
#2

Just a sidenote: Pow installer can be used easily in umbrella setups. You have to run mix pow.ecto.install -r MyApp.Repo in your ecto app and mix pow.phoenix.install -r MyApp.Repo in your phoenix app.

It’s impossible to know what kind of umbrella setup a project is, so this can’t be done automatically. However I think it would be useful to show a notice about how to do this when running pow.install in the umbrella app root.

I almost exclusively work with umbrella apps myself :smile:


#3

Great tidbits! I will try those. :slight_smile:

As for an installer, I would think something like mix pow.install --umbrella --new_app pow_auth could be enough? I don’t mean that your installer has to parse the structure of the umbrella app, no – I mean that with some more CLI options as you showed above, the Pow installer can just create an entirely separate app inside the umbrella that’s exclusively used for authentication with Pow?

Sorry if I am misunderstanding.

EDIT: I keep forgetting that eventually you do have to edit your web app’s router.ex file though.


#4

Unfortunately that would require that Pow also creates a Phoenix app which is outside the scope of the Pow mix tasks. Most of the time you would want to have you sign in and registration views the same place as your default Phoenix app.


#5

I understand. But as an unfortunate side effect any auth library one picks up becomes a strongly entrenched dependency inside your web app’s files which you cannot easily replace if something else is serving you better down the line.


#6

True. It’s been something I’ve experienced with other authentication libraries for Elixir/Phoenix, so I’ve tried to make a very transparent API for Pow that makes it easier to replace if something fits better down the line. The goal has been to have as few files as possible generated or modified, and make any developer understand how Pow works by taking it step by step to e.g. enable extensions, or modify templates.

With umbrella apps it’s possible to limit this, but you’ll have to work with multiple Phoenix apps, endpoints, cross check sessions, etc, and at that point a detached authentication server may make more sense.


#7

Outside of Pow and Guardian, the other authentication library/framework I’ve liked in this space is Phauxth. There’s been some recent updates, and the code is easy to read and well documented, so at least worth reading through if you’re planning on spinning your own authentication solution.

I’ve got a greenfield application scoped out that will need similar admin-invite capabilities, so I’m interested to see what solution you work out.


#8

If you’re willing to accept a token-based approach, I created Authex. It has support for authentication + authorization, is pretty minimal in its approach, and is umbrella friendly. It may work depending on your needs.

https://github.com/nsweeting/authex


#9

I’m really interested in what you don’t like? Are you not using the standard web structure?

Yes that is the downside of caching the user data. I’ve never come across the issue of a stale belongs_to, but completely understand the issue. The choices are either fetch the preloaded user schema on each request, or calling the API to update the cache.

BTW, if you want to fetch the user on each request, you could to that with a plug pretty easy. No changes to Coherence.

# lib/my_app_web/router.ex
def reload_user_data(%{assigns: %{current_user: %{id: id}}} = conn, _) do
    user = MyApp.Repo.get(id, preload: [...])
    assign(conn, :current_user, user)
end

def reload_user_data(conn, _) do
  conn
end

and then put that plug after the coherence plugs in each of your pipelines

Yes it is. Just don’t install the registerable option. This way, new accounts are either created somewhere in your app, or by sending them an invitation.

BTW, I have just overhauled custom controllers. The generated controllers are now < 10 lines long, with all the supporting actions and helpers overridable.

Thanks for sharing your experience with Coherence.


#10

Apologies to everyone for the silence, I will respond properly when I get some free time somewhere in the next 24h.


#11

My library should be able to cover all your use cases as it offers a vast amount of customization via a behavior. There are still some github issues I want to get to in the future that would make it an even better fit but it absolutely can do everything your looking for now in a fairly easy way. I should note that it is for api authentication and would still need a ui built around it. I made it for my project https://appdoctor.io . I currently only allow new accounts with a provided beta code. You can do much the same thing via your email strategy mentioned.

Here is a post I did a while back going into more detail on why I made it:

and any pull requests/contribution is VERY welcome!

A big plus at least for me is ease of use in a new project. It provides a drop in plug and macro to handle everything in 3 lines of code for auth.

Also mind you this solution was originally not made to be open sourced so a lot of the current github issues deal with braking away from what I had as original defaults.


#12

From the admin invite only standpoint, why not just put the sign up page behind admin access requirements? If an admin fills in the details and email address then the user can get the registration link to continue the process from there.

That part should be pretty simple to solve regardless of solution.


#13

Mostly because, as much as I worked with Elixir so far (soon 2 years), it hasn’t been with Phoenix at all. I was working on API gateways behind Guardian (and one of the customers already had a dedicated auth server). I also worked on various data crawlers and a lot of background tasks gated behind pools. So my exposure to this topic in Elixir land has been extremely limited. And when I found myself in a need of a quick solution, I have to fight an uphill battle to (a) inform myself of all the alternatives in a short time frame and (b) still work extra to custom-tailor them to my needs.

It’s hard to complain – at least there is a choice and all the libraries seem really well-made. But now I have to evaluate several other solutions with a time I don’t have (so I am using part of my otherwise free time). As much as I hate Rails, libraries like Devise can and do save time when you are building MVPs / prototypes / alpha versions.


#14

Things are moving too fast for me to give you a good breakdown (because half my complaints I don’t even remember now), but in a nutshell: I have to go my non-web and web app directories and execute commands there separately. My chief complaint was that I could do everything in myapp but when I wanted to get the generated controllers I had to go to myapp_web. And then they went into the wrong directory myapp_web/lib/_web for some reason (the generator didn’t detect the myapp part).

I would very much appreciate if community-made Phoenix generators – not only Coherence – allow the user to specify the app they want to inject the code into and to auto-detect the web app (scanning dependencies to look for phoenix, looking for MyApp.Web module, looking where router.ex is). It’s not a big deal per se but requires extra attention and it’s good that your mix tasks give proper informative output (kudos for that!) otherwise I’d be very quickly lost.

Do you mind repeating here on how I can do that? I looked at the docs and the way it was described there was the first thing I tried but it didn’t work for me. One of the things I also tried was fiddle with the functions in the schemas.ex – doing Repo.preload there worked immediately. I eventually settled for the generated controllers and overriding their private current_user function. I am looking at something as simple as updating the user and their attached belongs_to object (only when needed).

See, this is what only working with the domain / business logic code nets you. I keep forgetting about the beauties of Plug. Thanks for giving me a reality check!

That’s what I thought as well but haven’t tried it yet. Thanks for confirming. :+1:

Do you have a recommendation on how can I update my already generated controllers? Or is it just mix coh.install --reinstall and git diff after?

BTW, Coherence is still at 0.5.2 for me and Hex claims there’s no newer version. Should I be using the git master version to get the latest changes or are you releasing a new version soon?

Thanks a lot for being responsive!


#15

I am not sure I remember well but weren’t JWT tokens all based off of the same secret? In a few other auth frameworks (not only in Elixir) every user has their own signing salt / secret which I feel is more secure.

Also, how does it deal with token revocation?


#16

Your library looks promising. A few questions:

  1. Do you have plans to replace poison with jason as others did (including the core team)?
  2. I appreciate the opinionated approach – it saves code and time – and I usually code my internal libraries in the same manner. However in this case I absolutely need my auth library to work with an already existing model. Have you considered just offloading the DB responsibilities to an external (and configurable by the user) module? Example: Coherence generates schemas.ex file for you and you can just fetch users and other related data from practically anywhere if you change the functions. Enforcing such an external module via a behaviour would be even better.

I have to say that I read your README in GitHub and your linked article. I love your approach and was gravitating to something very similar myself. It’s just that the added views from other libraries are quite tempting and I am not sure I wanna reinvent them (but it might turn out easy).

I’ll keep this thread updated on what I choose and why. Haven’t made the choice yet, still making test apps and trying to evaluate objectively.


#17

I totally get that. I will most likely replace poison with jason in a coming version as to not add another dependency to new Phoenix applications. As for working with any schema it is a top priority in the rewrite currently going on as I realize my opinionated approach is a clear turnoff to some applications. Keep a look out and best of luck with what you chose!