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_required
function.
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.hashpwsalt
function.
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: "abc@gmail.com", 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_hash
field 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.