Writing a Blog Engine in Phoenix and Elixir: Part 2, Authorization

Brandon Richey
HackerNoon.com
18 min readOct 7, 2015

--

Last Updated At: 07/21/2016

Previous Post in this series

Current Versions:

As of the time of writing this, the current versions of our applications are:

  • Elixir: v1.3.1
  • Phoenix: v1.2.0
  • Ecto: v2.0.2
  • Comeonin: v2.5.2

If you are reading this and these are not the latest, let me know and I’ll update this tutorial accordingly.

Fixing up some bugs

If you’ve been following along, you should have a (somewhat) functional blog engine running in Elixir/Phoenix. If you’re anything like me, this sort of thing makes you giddy and you can’t wait to press ahead and have something even more polished!

If you want to check your progress, I’ve thrown all of the work we’ve put together online a github repo.

The first bug that’s pretty easy to reproduce is to go to http://localhost:4000/sessions/new and just hit submit. You should see an error message that looks like the following:

If we look at the create function in SessionController it’s pretty clear what’s happening here.

So if we send across a parameter string that includes a blank value (or no value) for username, we’ll hit this error. Let’s fix this up quickly; thankfully, this is pretty easy to fix up with guard clauses and pattern matching. Replace the current create function with the following:

We replace the params argument in the second create function with an underscore since we do not need to use the results from it anywhere. We also added a reference to this failed_login function, so let’s add it as a private function. In web/controllers/session_controller.ex and modify the Comeonin import statement at the top:

We need to call out to dummy_checkpw() so that someone cannot just iterate over usernames with bad passwords and check to see if the timing changes when usernames don’t exist in our system. Then, we add our failed_login function:

Again, note that call to dummy_checkpw() at the top! We also clear out the current_user session, set a flash message indicating that the user entered an invalid combination of usernames/passwords, and redirect back to the main index and halt. The call to halt here is sanity against double render issues.

And then everywhere we have any code that was doing any of the work above, we can replace that old code with calls to the new function.

That should take care of any existing odd bugs with logins, so we can move on to making our posts associated with the current logged in user.

Adding our migration

The first thing we need to do to be able to associate posts with users is modify the posts table to include a reference to the users table.

First, we’ll use Ecto’s migration generator to create a new migration.

And we should see some output letting us know that it worked successfully.

If we open up the file, however, there won’t be anything there yet, so we’ll add in the details.

We’ll change the change function to contain the following:

This will add the user_id column referencing the users table. It also sets up an index on the user_id column on the posts table. We’ll run mix ecto.migrate and start modifying our models now.

Associating posts with users

Let’s open up web/models/post.ex and add a reference to the User model.

Under the “posts” schema, add the following:

We’ll add an inverse relationship to the User model pointing back to the Post model. Under the “users” schema in web/models/user.ex, add the following:

We’ll need to also open up the Posts controller and associating our posts with users.

Modifying our routes

First, we’ll update the router to point to posts underneath a user. Open up web/router.ex and we’ll change the routes around a little.

We’ll change the “/users” and “/posts” routes slightly. Remove the two and replace instead with this block:

Fixing up our controller

If we try to run mix phoenix.routes right now, we’ll get an error. That’s okay, though! Since we changed our route structure, we lost the post_path helper, which has instead been replaced with the nested resource version of user_post_path. Nested helpers allow us to access routes that represent resources that require another resource to be present (such as posts underneath a user).

So, if we have a regular post_path helper, we call it this way:

The conn is our connection object, the :show is what action we’re linking to, and the third argument can either be a model or the id for the object. We could also do:

However, when we have a nested resource, the helpers have to change with the modification to our routes file. Given that posts are now nested under users, our routes change too:

Notice that the third argument changes to the top level of our nested resource, and each additional resource follows in order. Given our new understanding of this, it’s pretty clear where this is throwing us errors, so we’re going to want to clean this up. We’re going to want to expose the requested user to all of our controller actions. The best way for us to do this is via a plug, so we’ll open up web/controllers/post_controller.ex:

At the top, we’ll add a new plug call.

And then at the bottom we’ll write up our assign_user plug:

And then everywhere that post_path shows up, we’ll replace it with user_post_path:

Fixing up our templates

Our controller is no longer spitting out an error message, so now we’ll work on our templates. We took a bit of a shortcut by implementing a plug that affected all of our controller actions. By using the assign function on our connection object, we’ve exposed a variable that we can work with in our templates. We’ll have to modify quite a few templates, because everywhere we’re using the link helper with our post_path helper, we need to update that to user_post_path and make sure the first argument after the action is the user’s id.

web/templates/post/index.html.eex:

web/templates/post/show.html.eex:

web/templates/post/new.html.eex:

web/templates/post/edit.html.eex:

Now, as a sanity check, if we run mix phoenix.routes, we should see output and a successful compilation!

Hooking it all up in the controller

Now, all we need to do is finish hooking up our controller to use the new associations. First, we’ll fire up iex -S mix so we can learn a bit about how to fetch a user’s posts. Before we do that, though, it’ll help us out a little bit to set up a list of standard imports/aliases that will get loaded every time we load up an iex prompt inside of our project. Create a new file in the project root called .iex.exs (note the period at the start of the file name) and populate it with the following contents:

Now when iex starts up, we won’t have to do something like the following every single time:

Moving on, and running iex -S mix: we should already have at least one User in our repo. If not, please create one first. Then we’ll run:

We haven’t created any posts associated with a user yet, so it makes sense that we’re getting a blank list there. We used the assoc function from Ecto to give us a query that linked posts to a user. We also could have done the following:

Which instead would’ve given us a query with an inner join instead of a straight where clause on the user id. Take special care to look at the query that is being generated in both cases; it’s very handy to understand the SQL being generated behind the scenes any time you’re working with code that generates queries.

We could also use the preload function when we’re fetching posts to preload the users as well, such as the following:

We need to give ourselves to work with for messing around with these queries, so we’re going use Ecto’s build_assoc function. build_assoc takes the model we want to add an association to as the first argument, the association we want to hook into as an atom for the second argument, and the parameters for the third argument.

And on the last command, we should get the following output:

And we’ll just check the first result quickly:

Cool! Our experiment is doing precisely what we expect, so let’s go back to our controller (web/controllers/post_controller.ex) and start fixing up the code. For the index, action, we want all of the posts associated with a user, so let’s fix up the code. We’ll start with our index action:

We can then visit the posts index for user 1 and see a list of posts show up! But if we try to visit a post index for a user that doesn’t exist, we get an error message, which is not a great user experience, so let’s clean up our assign user plug.

Now, when we visit a post index for a user that doesn’t exist, we get a nice flash message and are kindly redirected back to the page path! Next, we need to change our new action:

We take our user model, pipe that into Ecto’s build_assoc function, tell it we need to build a post, and then pipe the resulting blank model into the Post.changeset function to get a blank changeset. We’ll follow the same pattern for our create method (except with our post_params intact):

And then modify our show, edit, update, and delete actions:

And when we test it all out, we should just see everything working! Except…any user can delete/edit/create new posts underneath any user id they want!

Restricting posting to users

We can’t very well release a blog engine that has a security hole like this, now can we? Let’s fix it by adding another plug that makes sure the current user is the same as the fetched user.

At the bottom, we’ll add a new function to web/controllers/post_controller.ex:

And at the top, we’ll add the plug call:

Now everything should be working just fine! Users have to be logged in to post and can only mess around with their own posts. All we need is to update our test suite to handle these changes and we should be all set. Let’s start by just running mix test to figure out where we’re at. Odds are you’re going to see an error message like the one below:

Unfortunately, we have to go in and change every instance of post_path to user_post_path again. And, in order to do so, we’re going to need to change our tests pretty drastically. We’ll start by adding a setup block to test/controllers/post_controller_text.exs:

There’s a lot going on here already. The first thing we’ve done is added a call to a create_user function that we need to write. We need some test helpers, so we’re going to add functions to handle these. Our create_user function just inserts a sample user into our Repo, so that’s why we’re pattern matching {:ok, user} from that function call.

Next, we have the conn = build_conn() call, which you’ve seen before. We then pipe the resulting conn into this login_user function. This connection posts to our login function, since all of our major post actions require a logged in user. One thing that’s very important here: we need to return the conn and carry it with us into each test. If we don’t do that, the user will not stay logged in!

Finally, we changed our return of that function to return the same standard :ok and :conn values, but now we also include one more :user entry into the dict.

Let’s take a look at the first test we have to modify:

Notice we changed the second argument to our “test” method to instead pattern match to a map containing the keys :conn and :user instead of just :conn. This ensures that we’re exposing the :user key that we were working with in our setup block. Other than that, we’ve just changed the post_path helper call to user_post_path and added the user as our third argument. Run this test explicitly now; you can target this test with a tag or by specifying the line number by running the command as:

Our test should now be green! Great! But let’s keep modifying these:

Nothing new here other than the change to the setup handler and the user post path, so we’ll move on.

Remember that we had to fetch each post by the user’s association, so we want to make sure we’re doing that here and changing all of the post_path calls.

Another easily modified test, so we’ll move to the next one where it’s more interesting. Remember again that we have to build/fetch all of our Posts underneath a user association, so we’re going to modify our “shows chosen resource” test:

Previously, we were setting post to a simple Repo.insert! %Post{}. That won’t work for us anymore as we need to build it with the appropriate association. Since this line appears pretty frequently in the remaining tests, we’ll write a helper method to make this simpler for us.

This method creates a valid post model under our user association, and then inserts it into the database. Note that Repo.insert! does not return {:ok, model}, instead it just returns the model!

Going back to our test that we were modifying, the rest is pretty boiler-plate. I’m going to post the rest of the tests below; you’ll just be repeating the same modifications over and over until they’re all taken care of.

When you’ve modified everything, you should be able to run mix test and get green tests!

Finally, we wrote some new code as plugs to handle user lookup and authorization, and we’ve tested the positive cases pretty well, but we should also add tests for the negative cases as well. We’ll start with a test for what happens when we try to access the posts listing for a user that does not exist:

We don’t have to include :user in our pattern match from the setup block here since we’re not using it anyways. We’re additionally asserting that the connection is halted at the end of it all.

Finally, we need to write a test for when we try to edit someone else’s post.

We create another user to act as our bad user and insert it into the Repo. Then, we attempt to access the edit action for the post under our first user. This will trigger the negative case of our authorize_user plug! Save this file and run mix test and we’ll wait for the results:

Whew! That was a lot! But, we now have a functional (and more protected blog), with posts being created under our users and we still have some good test coverage! Take a break, relax, go play some Monster Hunter Generations (or at least, that’s my plan)! We’ll continue this series of tutorials with adding admin roles, comments, Markdown support, and finally we’ll break into channels with a live commenting system!

If you’re interested in learning how to debug this application in development mode, then read more about debugging a Phoenix application here!

If you want to continue on the tutorial, then let’s move on!

Next post in this series

Check out my new book!

Hey everyone! If you liked what you read here and want to learn more with me, check out my new book on Elixir and Phoenix web development:

I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

--

--