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:
You should now see the following git history.
And now you can install the existing dependencies, and setup the database.
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.
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
Now that we’ve gotten rid of the old registration links we can update the actual registration form.
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.
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
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.
Trying it out
Let’s give the full registration workflow a go. We’ll fire up the server.
And then create a registration token.
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
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.
The only other test to update is
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.
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
This also requires a couple of new
Let’s give it another go.
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.
And the schema file would use a virtual field for the token_string:
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.
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:
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.
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!