In our last post we implemented the backend changes required for our new locked down registration workflow. Today we’ll hook in the front end changes.
If you’ve followed along with part 1 you can continue with the code from there. Otherwise you can grab today’s starting off point by:
Terminal
Terminal
You should now see the following git history.
Terminal
And now you can install the existing dependencies, and setup the database.
Terminal
Terminal
Updating the front end
This should go pretty quick, there isn’t too much we need to change to get the front end working.
The first thing we’ll do is update the router, specifically the registration routes.
/lib/reg_tokens_web/router.ex
All we’ve done is add the :token
parameter to the end of the route, this is going to break a bunch of templates that are currently using the registration route. With our new registration workflow it doesn’t make much sense to have a direct link to the registration page so we’ll remove all the registration links from the heex
templates.
/lib/reg_tokens_web/templates/layout/_user_menu.html.heex
/lib/reg_tokens_web/templates/user_confirmation/new.html.heex
/lib/reg_tokens_web/templates/user_confirmation/edit.html.heex
/lib/reg_tokens_web/templates/user_reset_password/edit.html.heex
/lib/reg_tokens_web/templates/user_reset_password/new.html.heex
/lib/reg_tokens_web/templates/user_session/new.html.heex
Now that we’ve gotten rid of the old registration links we can update the actual registration form.
/lib/reg_tokens_web/templates/user_registration/new.html.heex
The only changes we’ve made is to update the route in the form_for
tag (just adding in a @token
assign). We also added a new error tag (<%= error_tag f, :registration_token %>
) so any token errors will be displayed.
The final piece of the puzzle is to update the controller.
/lib/reg_tokens_web/controllers/user_registration_controller.ex
Both the new
and the create
functions have changed. The new
function pulls the registration token out of the params
map and passes this along to the form via the token: params["token"]
assignment. The create
function calls into the new backend registration function (register_user_with_token
) instead of register_user
.
And that’s it for the front end changes!
Before giving it a go, we can update Accounts.register_user
and make it a private function as it is no longer called outside of the Accounts
context module.
/lib/reg_tokens/accounts.ex
Trying it out
Let’s give the full registration workflow a go. We’ll fire up the server.
Terminal
And then create a registration token.
Terminal
Note: you’d likely want to build some sort of admin interface for generating and managing tokens, but that’s outside the scope of this article so we’ll just stick to generating the tokens in iex
.
Now with a valid token in hand we can navigate to the appropriate registration URL, in this case http://localhost:4000/users/register/4xY75rlRQK7FZ13HBJDZNrJvo0nRBM3eUL1HbuEao8E.
Since we generated an email scoped token, signing up with a different email will fail.
But with the correct email, we’re all good.
Nice! We just have a few tests to deal with.
Updating some tests
We need to make a small change to the user_session_controller
test. It has an assertion
for the registration link and since we’ve removed that link we also need to remove the assertion from the test.
/test/auth_w_invite_web/controllers/user_session_controller_test.exs
The only other test to update is user_registration_controller_test.exs
.
/test/reg_tokens_web/controllers/user_registration_controller_test.exs
Nothing tricky, all we’ve done is add a token as part of the registration path router calls. For everything other than the success path (i.e. everything other than creates account and logs the user in
) we don’t actually need a valid token so we’re just passing in a hard-coded default token.
Let’s give our tests a rip.
Terminal
Whoops!
You’ll recall we updated the original register_user
function to be a private function. One thing we missed is that the function is used in accounts_fixtures.ex
. We can see user_fixture/1
relies on our now private function.
We’ll update the fixture function to run against the repo
directly.
/test/support/fixtures/accounts_fixtures.ex
This also requires a couple of new alias
statements.
/test/support/fixtures/accounts_fixtures.ex
Let’s give it another go.
Terminal
Everything is looking good!
What if we don’t want to store the token string?
In part 1 we talked about the case where we are immediately using the registration token and thus don’t need to save the url encoded string token in the database. What would this look like? Pretty simple to be honest.
First off the migration would exclude the token_string
column, i.e.
/priv/repo/migrations/time_stamp
_create_registration_tokens.exs
And the schema file would use a virtual field for the token_string:
/lib/reg_tokens/accounts/registration_token.ex
That’s it! If you re-create your database with the new migration (i.e. mix do ecto.drop, ecto.create, ecto.migrate
) and run the code everything will work as before but the token_string
will no longer be saved. All the tests will also be passing as is (you’ll need to re-create the test db, i.e. MIX_ENV=test mix do ecto.drop, ecto.create, ecto.migrate
).
A couple of useful queries
So that does it for our implementation but I thought I would throw up a few queries that make use of the generated_by
field just for the heck of it.
I’ve created some test data that looks like the below.
What we have is 2 “root” users, Bob and Sally who registered with plain tokens, everyone else were “invited” and thus registered with tokens generated by an existing user. We can see Bob isn’t very social, he has only invited a single user, Jim. Sally on the other hand has invited Jill and Kelly, and then Jill invited Marley who in turn invited Paige.
If we want to trace the invite / referral path how would we do so? It’s easy to find the direct invites of a user, i.e.
no file
As expected this returns the 2 users directly invited by Sally.
If we want a full picture of the sign-ups directly and indirectly associated with Sally we can use a recursive query. This article provides a very nice introduction to PostgreSQL recursive queries.
In our case, we’d end up with a query like:
no file
Nice! We’re seeing all users who are in some way associated with Sally. The depth column gives us insight into how closely each user is associated to Sally.
We can change the where generated_by_user_id
clause to scope the results to a particular point in the tree, i.e.
no file
Summary
That’s it for this series of posts. We’ve updated the workflow around registrations without too much bother. That’s one of the great things about Phx.Gen.Auth
; it provides a solid, ready to go implementation out of the box… but is easy to customize to your particular needs. Being a mix task that injects code directly into your project makes for a very transparent and easy to customize authentication solution.
Today’s code is available on GitHub.
Thanks for reading and I hope you enjoyed the post!