Ash Fieldnotes: Ash Authentication
AshAuthentication gives you an easy to use batteries included authentication framework compatible with the Phoenix, and Ash framework.
Kicking off the series on the Ash Framework, we've got AshAuthentication & AshAuthenticationPhoenix.
For the purposes of this post, I'm going to treat them as one and the same. Your mileage may vary.
Though AshAuthentication is a relative newcomer to the Ash ecosystem, we're starting with it because any time I start a new project I assume it's going to have users and I want to start with getting them up and running.
So, off to the races!
Getting started
Local authentication (username and password)
AshAuthentication's getting started guide will quickly get you up to speed. Getting local authentication up and running, complete with password reset is as easy as following the docs, as described.
So far, so good.
Adding confirmation
But wait. You probably want to confirm the user's email address.
Ash's got you covered.
Jump on over to the confirmation add on docs.
On discovering the confirmation docs, which are as easy to find as searching AshHQ for confirm
it's easy to add, although it wasn't clear why this is an AddOn
or what that means from a framework patterns perspective.
At the point of first implementation this was missing a note about identities
, and needing a sender
, which I put up a patch for.
The sender
wasn't too hard, as you can build that off the sender example in the password reset section.
The identities
bit wasn't too hard to figure out how to fix, and when I looked at the docs for what exactly an Identity is, it was straight forward and made sense. (It's essentially representations of unique indices, composite or not, for the resource if you didn't want to click the link.)
The eager_check_with
that the compiler was yelling at me about, nested inside the identities
section was a bit harder to figure out.
Initially AshHq's docs search were pointing me to the attribute on the resource, which didn't tell me that all I needed to do was point the macro to MyApp.Accounts
and I'm done.
When I returned to the identity section of the docs though it made that very explicit, which was nice, and points out two important things.
- Ash's compiler is actually really user friendly, at least based on what I've experienced so far. It tells you exactly where in the DSL you need to go to fix something.
- Reading the docs is still important.
The compiler friendliness is great, because it keeps with the common theme in Elixir of having actually useful stack traces, which can be something that takes getting used to for folks coming from other languages and ecosystems.
To highlight this, here's a paste of one of the compiler errors
Compiling 6 files (.ex)
== Compilation error in file lib/my_app/accounts/resources/user.ex ==
** (Spark.Error.DslError) [MyApp.Accounts.User]
identities -> identity:
The email identity on the resource `MyApp.Accounts.User` needs the `eager_check_with` property set so that inhibited changes are still validated.
(spark 0.4.5) lib/spark/dsl/extension.ex:605: Spark.Dsl.Extension.raise_transformer_error/2
(elixir 1.14.2) lib/enum.ex:4751: Enumerable.List.reduce/3
(elixir 1.14.2) lib/enum.ex:2514: Enum.reduce_while/3
/Users/brittonbroderick/code/my_app/lib/my_app/accounts/resources/user.ex:1: (file)
(stdlib 4.1.1) erl_eval.erl:748: :erl_eval.do_apply/7
(stdlib 4.1.1) erl_eval.erl:961: :erl_eval.expr_list/7
(stdlib 4.1.1) erl_eval.erl:454: :erl_eval.expr/6
Reading the docs is still pretty important, although I feel like they behave weirdly for me sometimes on AshHq.
What about magic links?
Same as the initial login, registration, password reset flow. Grab the docs. And just copy, paste, and tweak to use your app modules and atoms.
Easy.
Senders and email or phone number
An important note for this one, is that the flexibility of the sender and the package allows you to easily implement confirmation on either an email address or phone number.
While this may not be a detail you necessarily care about, it's an interesting consideration that your auth layer isn't necessarily limited to email, as we're seeing a shift to using phone numbers in some use cases. (Acknowledging either case has different tradeoffs that may be relevant for you.)
Adding OAuth2
Now, we've covered all the basics.
But what about when you want to hand off all of those responsibilities to someone like Google?
Once again, Ash has got you covered. Head on over to the OAuth2 docs.
This one the hardest part was finding in what the values ought to have been for the OAuth2 required fields
authorization_params
site
authorize_url
client_id
redirect_uri
client_secret
token_url
user_url
Full disclosure, that isn't actually Ash's fault, and you just end up having to dig through the provider's docs, or another package's hard coding of these fields.
After you've got those in place you can fire it up and be good to go. If you're using Google they won't let you use localhost
as the redirect URL even in dev, so you'll have to fire up something like Ngrok, but otherwise you're good to go.
But I want to add my logo
Backing way up now, but you probably want your app to use your logo, rather than Ash's.
You can configure an Overrides module.
Now I did this piece a few days ago, and I remember struggling to find the overrides module a bit, but can't recall why, so as long as you find the docs here, it's a trivial affair as well. I ended up finding how to configure the Override through a combination of browsing the Discord and spelunking in the AshAuthenticationPhoenix.
Conclusion
That's all there is to it.
Both to implement authentication with AshAuthentication, which is easy, and to the available functionality. I've only left some finer configuration you can do with all of this uncovered.
If you're me, at this point the only thing left on your authentication wish list is multi-factor authentication.
As far as I know that's not on the roadmap yet. but given the extensibility of Ash, which I'll cover in a later post, you (and I) have the opportunity to be the change we want to see in the world.
I'll keep it going in the next post in the series.