Basic Authentication In Your Phoenix App

It’s easy when you use Plug

It turns out that implementing HTTP Basic Authentication in your Phoenix app is easy when you use plugs. Let’s first see how the plug itself works:

Adding a Module Plug

This method of adding a Plug means that we add the Plug functionality inside a self-contained module.

When the plug gets called, we’ll receive a username and password from our app’s configuration, which represents the correct auth details. (We’ll see how this works later.)

If this is the first time the browser requests a resource that uses this Plug, it won’t be sending any attemped auth details yet, because the browser hasn’t yet found out that it needs to! So we respond by telling it that it’s unauthorized and should come back to us with some attempted auth details:

It should send back some attempted auth details. We send back a www-authenticate header in the response with the name of the realm, which can be anything, but usually some indication of what is being protected. (e.g. “My Admin Dashboard”) It’s unauthorized. We send back a 401 unauthorized response. We halt the Plug pipeline to stop processing further down the pipeline and return the connection.

Now the browser will present the user with a login popup, and will now send us back some attempted auth details. HTTP Basic Authentication works by comparing the Base64-encoded attempted auth details with the correct auth details. Because the user has typed in some login details in the popup, we now receive those attempted auth details in the authorization request header.

In order to do the comparison, we have to take our correct auth details, concatenate them with a colon and Base-64 encode them (because this is the format the browser sends us). If the encoded correct and attempted auth details are the same, we allow the Plug pipeline to continue by returning the connection. If they are different, we send a 401 unauthorized response back and halt the connection, like we did the first time.

Easy, isn’t it? Let’s add the Plug to our router and configure it to give it some correct auth details.

Routing to the Plug

In your router, create a new pipeline and pipe your protected actions’ scope through this pipeline:

As you can see, this is where the configuration holding the correct auth details is provided as the second parameter (in the form of a keyword list) to the Plug.

Further configuration of the Plug

It’s a bad idea to set the username and password directly in the router. You might want different details for different environments (e.g. dev, staging, production), and you’ll likely want to set them dynamically through environment variables. We can solve this problem easily by instead storing the plug configuration in our Phoenix config files.

In your Phoenix config file, add the following:

config :my_app, BasicAuth, username: “admin”, password: “secret”

Now, go back to the router and change the line calling the plug to:

plug BasicAuth, Application.fetch_env!(:my_app, BasicAuth)

This fetches the environment configuration from the Phoenix config files and provides it to the plug. That’s it!

Your actions are now protected by HTTP Basic Authentication!

Why this is the perfect solution for me

The app I’m building has dynamically added content but no User model. I didn’t want to add all the complexity user authentication brings just so I could secure my simple admin dashboard.

I’m transmitting over HTTPS, so the attempted auth details are sent to the server securely.

Why this might not work for you

You shouldn’t use this approach if you are using plain HTTP without a secure transport layer. If you are using this insecure method, the attempted auth details will be transmitted from the browser to the server insecurely. Sure, they’ll be encoded in Base64, but that’s essentially just transmitting them out in the open. If you must use insecure HTTP, look into HTTP digest authentication as an alternative. But, why aren’t you using HTTPS?

You might already have a User model and some sort of role-based authorization. In that case, you wouldn’t need this! You can let the right kind of users in.

You can find all the source code for this project on GitHub, and you can find me on the Elixir Slack and on Twitter as paulfedory. Thanks for reading!