Updated on 15 August 2020 .

Let’s look on what HTTP Basic authentication is and how to implement and test the HTTP Basic authentication in a Phoenix web application.

Basic is one of the authentication schemes we can use to authenticate access on the web (other is for example a Bearer scheme for OAuth 2.0 tokens). Using the Basic scheme is very simple. If our server responds with 401 Unauthorized response including WWW-Authenticate response header with a Basic challenge as follows:

WWW-Authenticate: Basic realm="Access to the application"

the browser can automatically ask the user for login credentials (login and password). The browser then connects the user and password together (separated by colon) and uses Base64 encoding to create a single string that is provided in the request Authorization header:

Authorization: Basic am1lbm9oZXNsbw==



One important thing to realize is that this header will now be sent by browser automatically with subsequent requests as long as they remember it (until browser restarts). Secondly that encoding password using Base64 is not a security feature and you need HTTPS.

Usually, we don’t dig the details of this challenge as authentication libraries or frameworks do this for us. In Elixir world we can reach for BasicAuth plug:

# mix.exs ... { :basic_auth , "~> 2.2.2" } ...

Once added, fetched, and compiled we can use it as any other plug in the Phoenix router:

... pipeline :basic_auth do plug BasicAuth , callback: & MyAppWeb . Protected . Authentication . authenticate / 3 , custom_response: & MyAppWeb . Protected . Helpers . unauthorized_response / 1 end scope "/protected_resource" , MyAppWeb . Protected do pipe_through [ :basic_auth ] ... end ...

We had to define a callback function that will check that the credentials provided are valid, and optionally custom_response function where we can customize the error message:

# Authentication defmodule MyAppWeb . Protected . Authentication do import Plug . Conn def authenticate ( conn , login , password ) do case check_login_and_password ( login , passwod ) true -> conn |> assign ( :user_id , user . id ) false -> halt ( conn ) end end def check_login_and_password ( login , passwod ) # check end end # Custom response def unauthorized_response ( conn ) do conn |> Plug . Conn . put_resp_content_type ( "application/json" ) |> Plug . Conn . send_resp ( 401 , ~s[{"message": "Unauthorized"}] ) end

UPDATE: The above way got deprecated and now issues the following warning:

warning: BasicAuth.init/1 is deprecated

We have to rewrite it as our function plug taking advantage of the BasicAuth module functions directly:

# in router pipeline :basic_auth do plug :my_basic_auth end defp my_basic_auth ( conn , _opts ) do { user , pass } = Plug . BasicAuth . parse_basic_auth ( conn ) case MyAppWeb . API . Authentication . authenticate ( conn , user , pass ) do { :ok , conn } -> conn { :error , conn } -> conn |> MyAppWeb . API . Helpers . unauthorized_response () end end # Authentication defmodule MyAppWeb . Protected . Authentication do import Plug . Conn def authenticate ( conn , login , password ) do case check_login_and_password ( login , passwod ) true -> conn = conn |> assign ( :user_id , user . id ) { :ok , conn } false -> { :error , halt ( conn )} end end def check_login_and_password ( login , passwod ) # check end end

What we do is parsing the user and pass combination with parse_basic_auth/1 function and then returning the conn in tuples to differentiate success from failure.

Once we have this ready, we can test it. The README of the BasicAuth plug gives us a hint on a using_basic_auth helper:

# example test defmodule MyAppWeb . Protected . SampleControllerTest do use MyWeb . ConnCase @username Application . get_env ( :myapp , :basic_auth )[ :username ] @password Application . get_env ( :myapp , :basic_auth )[ :password ] describe "GET /protected_resource" do test "shows protected resource when credentials are valid" , %{} do response = build_conn () |> using_basic_auth ( @username , @password ) |> get ( Routes . sample_path ( conn , :index )) assert response . status == 200 end test "error when credentials are invalid" , %{} do response = build_conn () |> using_basic_auth ( @username , "wrongpasswd" ) |> get ( Routes . sample_path ( conn , :index )) assert response . status == 401 end end defp using_basic_auth ( conn , username , password ) do header_content = "Basic " <> Base . encode64 ( " #{ username } : #{ password } " ) conn |> put_req_header ( "authorization" , header_content ) end end

Apart from the authentication helper that we pipe through with our connection we are also using valid credentials by default that we put to the text.exs file:

# Successful login for tests config :myapp , :basic_auth , username: "test" , password: "12dd9c47538129c7b48916d5d928e444"

Of course it’s up to you to make sure that a user with such credentials exist during the test run.

If we want to provide a “log out” option for the users, we can do so by sending wrong credentials to a protected resource (but they will see a new login popup).

Finally, if you are authorizing users to manipulate data on the server, make sure to use CSRF protection, because as I stated in the beginning, the Authorization header is sent automatically by the browser.

Published on 31 March 2019 . Tags: authentication elixir phoenix security testing

Any comments? Write me a DM on Twitter.