Building a performant real-time web app with Ember Fastboot and Phoenix (Part 2)

Users and Authentication (API)

This article is part 2 in a series. You should first read the previous parts in this series, or it may not make much sense!
Part 1: The meeting of two well-aligned, opinionated frameworks
Part 2: Users and Authentication (API)
Part 2.1: Some small adjustments to your API
Part 3: Users and Authentication (UI)
Part 4: Logging in to our API & ember-simple-auth
Part 5: Building a CRUD resource
Part 6: Animating your UI with liquid-fire
Part 7: Room UI & Messages

If you come from the Rails world, you may find yourself asking “where is my turnkey user account & authentication library?”. Devise was relied upon by countless ruby developers as the easiest way to add this kind of thing to their apps. Josè Valim, creator of Elixir says he has no plans to add this kind of thing to the Elixir world

Jose had a role in building Devise, and yet has no plans to bring something similar to Phoenix

The idea is, that Elixir and Phoenix provide the right primitives so that one can build things (like a reasonable auth solution) easily themselves. Among the benefits of this approach are that you don’t end up shoehorning a one-size-fits-all solution into your app, when it may not necessarily fit your needs, and you end up with better awareness of how your app works. If we were building a Rails API, and used Devise for our auth solution, we’d have to jump through a bunch of hoops and bypass large portions of the library to get what we needed — the point is that this is not a good pattern.

In our case, we’ll be using guardian, a simple Elixir library similar to warden (another staple of the Ruby world). Guardian is not turnkey, but aims to make building your own solution relatively easy, without adding a bunch of mysterious junk to your app that you may or may not need.

Guardian

Initial Setup

Let’s begin by installing Guardian, and add it as a dependency to our mix file (./mix.exs)

To actually get and build the package, run

mix deps.get

Then, go to your app configuration (./config/config.exs) and set guardian’s configuration up

We’ll need to generate a secret_key, which will be used to sign our authentication tokens. You’re going to want to set up a different key for each environment, but we can do this pretty easily using an environment variable

Phoenix comes with a little secret generation mix task that makes this easy

mix phoenix.gen.secret

You’ll see a secret written out to the console. Generate one for development, and one for production. It would be a good idea to make one for any other environment you have plans for (i.e., staging, test) as well. Then, update your config.exs to use an environment variable, and fall back to a hardcoded value (your dev key)

Secret from environment variable supersedes a hardcoded value

Set up your prod key in heroku by setting the appropriate environment variable, and you’re good to go.

heroku config:set GUARDIAN_SECRET=dJ8HdOlEp6T/Q8…6s/8ET2

Users

Initial setup

Next, we’ll need some concept of user accounts in our API. Phoenix makes this really easy to set up. We’ll start by treating users as a general CRUD resource, and then modify it from there. Run the following in your terminal

mix phoenix.gen.model User users email:string password_hash:string

This will build a few things for you:

  • A database migration
  • A model
  • Tests for the model

We’ll want to make one quick change to the database migration to ensure that email address uniqueness is enforced via a DB index (not just by application logic)

Open up the file that was just created for you in the ./priv/repo/migrations/ folder, and add a new index in the create method. It should now look like this

Adding a DB index to ensure user email address uniqueness in your system

Your Postgres database probably doesn’t exist yet, so you’ll want to create it and then run the migration that will set your users table up. Mix provides a “do” task that allows you to run multiple tasks in sequence.

mix do ecto.create, ecto.migrate
MIX_ENV=test mix do ecto.create, ecto.migrate

The second command above does the same thing for your local test database.

User Passwords

We’re going to have to alter our user model a little bit from what Phoenix created for us, because saving passwords in plain text is silly. What we want is the ability to receive a password and a password confirmation from the user registration process, and save a hashed version of the password, using an algorithm that is resistant to brute force attacks like bcrypt. This approach presents two types of advantages

  • You’ll be storing the hash of the user’s password instead of the password its self (or an encrypted password). Using a one-way hash means that if an old database backup is stolen or something, the hashes can’t be turned back into passwords (and tried on other sites!).
  • Your system will be resistant to brute force attacks, whereby a malicious party attempts to guess a bunch of passwords with the hope of guessing one with the right “hash” to login as a particular user

Let’s install another elixir library called ComeOnIn to help us with this task. Go to your ./mix.exs file and add it as a dependency

And list it as an application dependency

Run

mix deps.get

to finish have mix download the package for you, and give yourself a fist bump 👊.

Head on over to your user model (./web/models/user.ex) and add two new “virtual” fields for password and password_confirmation.

Virtual fields are used for object creation and validation, but are not serialized to the database. Perfect for our purposes. Next, update the list of required fields to include both password and password_confirmation. Remove password_hash while you’re at it, since that’s an internal concern of the User object.

Next, create a private method called hash_password, which will take the password property and hash it using bcrypt. This method should only run if the changeset (proposed DB modification) is valid, which we can take care of via some easy pattern matching.

Finally, we can put a couple of other validations in place, just to make sure everything is neat and tidy. Your user.ex should now look something like this.

If you run your tests

mix test

you’ll see that you have one test failure — let’s go fix it! Open up ./test/models/user_test.exs and update the valid_attrs property to look more like what you’re validating for

@valid_attrs %{email: “mike@example.com”,
password: “abcde12345”,
password_confirmation: “abcde12345”}

We can add a few more negative test cases if you like

Token Serializer

Next, we’ll need to set up a token serializer. This is essentially what serializes and deserializes JSON Web Tokens (JWTs). We’ll use the basic setup suggested in the library’s readme. Create a file called ./lib/guardian_serializer.ex that looks like this

Registration & Login (API)

Registration: where we are going

Our user model is pretty simple, and JSON-API tells us what our JSON payload should look like.

{
"data": {
"type": "user",
"attributes": {
"email": <user email>,
"password": <user password>,
"password_confirmation": <user password>
}
}
}

Let’s plan for our API endpoint to be

POST /api/register

Once we get a few steps further in this project, we can send out a confirmation email and potentially log the user in immediately, but for now let’s just indicate success or failure.

Login: where we are going

Now we need a means of allowing the user to log in. Whenever possible, it’s a good idea to align with existing standards rather than “going cowboy” with this kind of thing, so we’ll implement an API endpoint that looks like the OAuth2 password grant specification. We’ll want to receive a JSON payload that looks like this

{
"grant_type": "password",
"username": <user email>,
"password": <user password>
}

and return a response that looks like this

{
"auth_token": <JWT from Guardian>
}

There are many other aspects to authentication that we can address later, but this will be enough for us to get by for now. The API endpoint is kind of arbitrary, but let’s just use

POST /api/token

for consistency with other OAuth2 providers.

Registration: server-side implementation

In the end, it’ll be nice if we have a RegistrationController and a SessionController, to handle new user creation, and login/logout, respectively. Create a new file called ./web/controllers/registration_controller.ex, that looks like this

Here, we’re using pattern matching to validate the structure of the API payload we’re looking for, and Phoenix will automatically return a 422 error (bad request) in the event that the payload doesn’t match this form. This is handy because we can be assured that basic validation has already taken place once we’re inside this create method

Hop on over to your router, and hook this new controller up to the API endpoint we are building. It should now look something like this

Now, let’s TDD this just for fun! Create a test file ./test/controllers/registration_controller_test.exs

And running the tests should show two tests failing. Create a new file called ./web/views/user_view.ex and set it up like this

And then go back to your controller, and flesh out the create method

Run your tests again, they should now pass. Let’s see if we can run a simple CURL command to verify that our API endpoint works properly. Start your API up

mix phoenix.server

and then run this CURL command to create a user

curl -XPOST -H "Content-type: application/json" -d '{"data": {"type": "user", "attributes": {"email": "mike@example.com", "password": "abcde12345", "password_confirmation": "abcde12345"}}}' 'http://localhost:4000/api/register'

You should see a response in your console indicating a response code of 201 (created), and debug logging that indicates a successful database INSERT operation. You should also see that instead of writing the password to the DB directly, a hash is used instead.

Successfully creating our first user!

For now, let’s leave this registration stuff, and deal with logging in, as this new user we just created

Logging In: server-side implementation

Let’s begin by fixing up our route and controller situation so it is in alignment with the planned approach to login I’ve described above. Head on over to your router (./web/router.ex), and replace the existing line directing traffic to the SessionController with this

post “token”, SessionController, :create, as: :login

What this is saying is that POST requests to the endpoint /api/token should be routed to the create method on the SessionController — and that within our code, we wish to refer to this route as login. Your router should now look like this

Just for fun, let’s now use another one of Phoenix’s built-in mix tasks to see the current routing table for our app. Go to your trust terminal and run

mix phoenix.routes

You should see something like this

registration_path POST /api/register Peepchat.RegistrationController :create
login_path POST /api/token Peepchat.SessionController :create

Now, let’s go over to our SessionController (./web/controllers/session_controller.ex) and implement this method. Get rid of the existing index method we placed there earlier during the initial setup — we won’t need that anymore.

Let’s begin with the method signature. We want to use pattern matching once again to ensure that we filter out incoming requests that aren’t structured the way we want. It’s also worth noting that there are multiple different types of OAuth2 “grant types”, and it would be nice to have different controller actions to handle each. If we receive a request for a grant type we don’t understand, we should throw a reasonable error.

Handling unexpected grant types gracefully

Now let’s implement the body of the login method. This comes down to a few simple steps:

  • Lookup a single user by username (fast, thanks to our unique DB index)
  • Hash the password passed in as an API argument
  • Compare that hash to the hashed value retrieved from the DB
  • If everything looks good, create a token, and pass it back in the API response
  • If anything looks bad, return a 401 (unauthorized) error

Here’s the code that essentially does this, with some useful logging:

Let’s try to login with a CURL command, using the credentials we created while building our registration controller.

curl -XPOST -H "Content-type: application/json" -d '{"grant_type": "password", "username": "mike@example.com", "password": "abcde12345"}' 'http://localhost:4000/api/token'

You should get something back like this if you’re successful

{“access_token”:”eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJVc2VyOjEiLCJleH.....NE3hPQP3BO26Y7Pox1eLw6K74w”}

Now try logging in with the wrong password by altering the appropriate part of the CURL command above — you’ll get a bunch of junk logged to the console. This is because Phoenix is attempting to do you a favor by rendering errors in HTML. We’ll want to disable this feature, and return errors in the appropriate JSON-API format.

Rendering Errors

We’ll need to do a few things to get errors aligned with the JSON-API standard. The first is to turn off the fancy HTML stack traces that Phoenix renders for you. Go to ./config/dev.exs and find the line

debug_errors: true,

Delete it, and restart your API (as you have to do every time you change a config, or other critical files).

Now try logging in with the wrong password again, using a CURL like this

curl -XPOST -H "Content-type: application/json" -d '{"grant_type": "password", "username": "mike@example.com", "password": "wrong_password"}' 'http://localhost:4000/api/token'

And you’ll get a JSON error message this time

{“errors”:{“detail”:”Server internal error”}}

But this still isn’t aligned with the JSON-API format. To get what we want, we’ll reach for another library called ja_serializer. Head over to your mixfile (./mix.exs) and add ja_serializer as a new dependency

and run

mix deps.get

to download the library.

Head over to ./web/views/error_view.ex and update it so that it looks like this

You can add as many errors as you want here, and then render them by status code (really it’s template name, technically). Try that incorrect login CURL one more time and you’ll now get

{“errors”:[{“title”:”Unauthorized”, ”code”:401}]}

This is the format we’re looking for. One last thing we should do while we’re thinking about this is ensure that server-side validations are rendered correctly as well. Create a file ./web/views/changeset_view.ex that looks like this

We can test that this is working by attempting to register a user with a username that doesn’t look like an email address.

curl -XPOST -H "Content-type: application/json" -d '{"data": {"type": "user", "attributes": {"email": "mike____example.com", "password": "abcde12345", "password_confirmation": "abcde12345"}}}' 'http://localhost:4000/api/register'

You’ll get back something like this:

{
“errors”: [{
“title”: “has invalid format”,
“source”: {
“pointer”: “/data/attributes/email”
},
“detail”: “Email has invalid format”
}]
}

Take a look at how fast those server-side validations come back. Yes, it’s that fast, and yes you’ll have to explain to your Java developer buddies that this is probably several hundred times faster than doing a similar thing with their favorite technology.

[info] Sent 422 in 290µs

Nice work! We should be done with the API now.

UPDATE: As I was writing more content for this series of articles, I had to make some additions to Part 2 (what you’re reading now). Please take a look at Part 2.1 before proceeding to Part 3, where we’ll start working with Material Design, and build out the UI side of user registration.

Like what you read? Give Mike North a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.