Pow provides an excellent authentication solution for Phoenix HTML applications; but what if you are looking to build out an API? Recently a new API guide has been published, and in this post we’ll have a look at how easy it is to use Pow in the context of an API.
I am constantly impressed with Pow
. New features and enhancements are regularily implemented, and a quick glance at the commit history shows it to be a very active project. Like much of the Pow
documentation, the newly added API guide is pretty comprehensive and provides a good description of how to get things up and running. I figured an example of using a Pow
enabled API might be useful however; so even though we’ll largely be following the guide, we’ll add in some extra endpoints to demonstrate Pow
API authentication in action.
Creating our app
For demonstration purposes, we’ll build a very simple REST API. We’ll pretend we are building out a basic (super, super, basic) warehouse inventory system, so we’ll call our application warehouse_api
.
Let’s create the API!
Terminal
Choose Y
when asked to fetch and install dependencies. Now we’ll change into the application directory and create the database.
Terminal
We’ll use a Phoenix generator to create a simple inventory
context with associated endpoints for managing the products
in our warehouse.
Terminal
As per the mix
output, let’s run the migrations…
Terminal
… and update the routes.
/lib/warehouse_web_api/router.ex …line 8
That’s all we need for our basic application, let’s fire things up and hit a few endpoints.
Interacting with the API
I am going to be using Insomnia to send requests to the API, but you can use which-ever REST
client you prefer.
First off we need to start the server.
Terminal
Now let’s see if we can create a product.
We’ll create a new request within Insomnia:
We’ll give the request a name of Create Product
; select POST
as the request method; and specify JSON
as the request body format.
We need to set the URL
to localhost:4000/api/products
. The request body needs to include a top level product
key, and under that, a name
and quantity
.
When we send our request via the Send
button we’ll see that a product has been created!
Fantastic! Let’s set-up one more API call in Insomnia
before moving onto getting Pow
up and running. We’ll set-up a call to retrieve all the current products in our warehouse. This will need to be a GET
request.
We don’t need a body this time, so we set the URL
to localhost:4000/api/products
and we’re good to go.
Great, so we have some API endpoints we can play around with, let’s see how to go about securing our API with Pow
.
Using Pow within our API
Basic Installation
The first step is to add pow
to our list of dependencies.
/mix.exs …line 34
Now we need to grab our dependencies via mix deps.get
.
Terminal
And install Pow
.
Terminal
We now have some configuration to deal with, but first let’s migrate the database to create the Pow
specific tables.
Terminal
Nice! … onto configuration.
Configuration
Only a small configuration change is needed, we just need to add a Pow
configuration section to config.ex
. I’ve placed this above the logger configuration section.
/config/config.exs
That’s it for configuration, now we need some router updates.
Router updates
There are a few things we need to update with our router
. First off we need to update the existing api pipeline
to include a custom authentication plug
. In this way our authentication rules will be run on every request. We’ll create this plug
after we’re done updating our router.
We also need to add a new pipeline
for routes that require authentication.
/lib/warehouse_web_api/routes.ex
In the api_protected
pipeline we’re referencing a custom error_handler
module which we’ll create after finishing the router updates.
As far as the actual routes go, we need to add some Pow
specific routes for user creation and session management.
While we are at it, we’ll also update the routes for our Product API endpoint. We’ll require authentication for any actions on the endpoint other than the listing and viewing of products. We can do this by adding a second api
scope that pipes through both :api
and :api_protected
. Note: we could add the resources "/products", ProductController, only: [:show, :index]
routes in the same scope as the Pow
routes. I kind of like keeping the Pow
routes in a seperate scope, but this is strictly a personal preference.
/lib/warehouse_web_api/routes.ex
That’s it for the router
, we now need to go about adding our custom authorization plug, error handler and some Pow
specific controllers.
Adding Pow specific plugs and controllers
We need to add an authentication plug, an error handling module and controller modules for both user registration and session management. To accomplish this we’ll be using the code referenced in API guide pretty much verbatim:
Let’s start with the authorization plug.
Terminal
/lib/warehouse_api_web/api_auth_plug.ex
So a fair bit of code, but it is all pretty self explanatory, basically everything to do with managing sessions and tokens is handled in the plug.
Next we’ll create the error handler.
Terminal
/lib/warehouse_api_web/api_auth_error_handler.ex
Simple… when we encounter an unauthenticated request for a protected route, we just return a 401
status code and an error message.
Finally the Pow
specific controllers.
Terminal
/lib/warehouse_api_web/controllers/registration_controller.ex
The registration controller provides a single endpoint for creating new users.
And here is the session controller.
/lib/warehouse_api_web/controllers/session_controller.ex
A little more involved, we have 3 functions; one for logging in (create); one for logout (delete); and one for token renewal.
And that’s it! We’re good to go, so let’s see our newly protected API in action.
Checking out the Pow functionality
Let’s fire up the server.
Terminal
First thing we will do is run our List Products
request from Insomnia… since nothing has changed with this route, everything should still be all good:
Nice, still working!
Let’s try creating another product.
No dice… this is what we are expecting as we’ve protected the create route. We’ll need to be authenticated in order to create a new product.
Before logging in, let’s update the create function. We’re just going to add a debug line to inspect the current_user
value that Pow
makes available to us. Once we log in and create a product we should see a current_user
showing up in the console.
/lib/warehouse_api_web/controllers/product_controller.ex …line 14
Ok, with that out of the way, let’s register a user. We need to create another POST
request, again with a JSON
body.
Provide a URL
of localhost:4000/api/registration
and the following body:
Running the request, gives us a new user:
The registration response also gives us a token we can now use in calling the create product endpoint.
Add a new Authorization Header
to the create product request, and fill in the token value from the registration request.
Now running the create product request succeeds!
Let’s double-check everything by logging out the user, ensuring we can’t create a product and then logging in the user and ensuring product creation is again a-ok.
So first to log out, we need to create a DELETE
request.
We need to use a URL
of localhost:4000/api/session
and we need to provide the Authorization header. The header value is how Pow
knows which token to invalidate.
Running the endpoint results in an empty response.
What happens now, if we attempt to create a new product?
As expected we now get a 401
error.
Let’s make sure we can log in.
We need to create a POST
request.
The URL
to use is localhost:4000/api/session
, and the JSON body is:
Sending the request yields a new token:
Now if we update the Authorization header value in the create product request, we’ll be able to create products again.
Sweet! If we glance at our console we can see we also have programmatic access to the current user.
This is useful if for instance you’ve set up some roles you need programmatic access to.
Summary
That concludes today’s post. Setting up API authentication with Pow
turns out to be pretty easy!
I had previously looked at using Pow
with an API and wasn’t too sure how to go about it, so it’s awesome to see the documentation and guides continue to evolve.
Note: the API guide also includes some example test modules for testing the api_auth_plug
, registration_controller
, and session_controller
. It’s worth having a glance at them and you’d likely want to include something similar in your project.
Thanks for reading and I hope you enjoyed the post!