API Authentication in Elixir Phoenix - Part 1

API Authentication in Elixir Phoenix - Part 1

Authentication is a very important aspect of any application. It restricts the unknown user to access anyone's data. We will be going to implement token-based authentication. This blog will be a continuation of our blog "JSON-API with Elixir-phoenix". We developed a "post" resource with an index action. Right now that resource is public and anyone can access those blog lists. Imagine, if you want that endpoint to be accessed only by the registered user. How this can be achieved? The answer is authentication. There are many strategies to authenticate a user. We are going to implement token-based authentication in Elixir-Phoenix.

This blog is 2 part series.

  • Part 1: It will be about securing user password as hash using Bcrypt strategy (This is Part 1)
  • Part 2: We will be adding a secured password hash in API flow.

We are going to split this blog into 3 parts:

  • Hashing Password
  • Storing Password as a hash
  • Testing the password hash

Password Hashing:

We don't store user passwords directly in the database, instead, we save password_hash. We will be using the Bcrypt algorithm for hashing our passwords. There is no way to backtrack or get a password from password_hash. It is one of the very secure encryption algorithms for hashing passwords. There is a library called comeonein in a hex that will help manage password salting and hashing using bcrypt so that we can avoid low-level details.

We are going to install comeonein and bcrypt_elixir for hashing strategy.

We are going to create a migration schema for our user's table. mix phx.gen.context Blog User users email:string username:string password_hash:string

This will create migration files, now we will run this migration by running mix ecto.migrate.

Now you open the User module, where you have the schema and changeset function you defined while creating migration. We have to do some extra work before storing password_hash. For that we are going to add two extra fields password and password_confirmation in the User schema, which are going to be virtual since we are not going to store that.

We are going to replace password_hash field with password and password_confirmation in cast and validate_requiredfunction. We are also going to add validation for the uniqueness of our email. We are going to add unsafe_validate_required in function pipeline after validate_required. Also, for password confirmation, we are going to add the validate_confirmation function from ecto changeset.

So below are the changes till now in the changeset function.

  def changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:email, :username, :password, :password_confirmation])
    |> validate_required([:email, :username, :password, :password_confirmation])
    |> unsafe_validate_unique([:email], BlogApi.Repo)
    |> validate_confirmation(:password)
  end

Storing Password as a hash:

Till now we have focussed only on adding the password and confirmation fields in the changeset pipeline. Now we will be working on hashing of the password. We are going to add one more function in the changeset pipeline which is going to convert user-entered passwords into hash. So, we create a function called hash_password which will take changeset as a parameter. We will hash the password using Bcrypt.hashpwsaltfunction.

def hash_password(changeset) do
  hash = Bcrypt.hashpwsalt(get_field(changeset, :password))
  put_change(changeset, :password_hash, hash)
end

This is going to hash the password and set it on the password field in the schema and return the updated changeset.

Testing the password hash:

Now, we are going to test the password hash in the iex shell. Turn over to your terminal and run iex -S mix and alias User and Repo module. Create a variable `hash=%{email: "", password: "password", password_confirmation: "password"}``

Now, will create the changeset, user = User.changeset(%User{}, hash) This is going to changeset struct as given below:

  #Ecto.Changeset<
    action: nil,
    changes: %{
      email: "abc@gmail.com",
      password: "password",
      password_confirmation: "password",
      password_hash: "$2b$12$iMn.3MoDR12Ryw/1M8enPeYqiqlHS.P5PGNWYKKDjjA8dWU0OyzGG"
    },
    errors: [],
    data: #BlogApi.Blog.User<>,
    valid?: true
  >

As you can see it has generated the struct with some random hash in the password_hashfield and there is no error in the errors field. Now, we will commit this changeset to the Repo by running Repo.insert(user).

This will push changes to the database. We can confirm this by running the following command Repo.get!(User, 1).

This is the end of part 1, where we have encrypted the password. In the second part, we will add this password encryption in the API Authentication flow.

Did you find this article valuable?

Support AbulAsar S. by becoming a sponsor. Any amount is appreciated!