Showing posts with label password. Show all posts
Showing posts with label password. Show all posts

Elixir: Forgot Password

After having deployed your account management system, you’ll (very) soon discover that a good password is a one time (at least) lost password. The remedy is the now common forgot password feature that you’ll need to add soon because sending new passwords is not safe and you’ll not explain to your customers how to setup GPG.

That’s why the easiest way is to define a process for customers to define new password for their account:
  • The user forgot his password
  • It will fill a form with his account email
  • We need to send to him a temporary route (using a temporary token) that he will use to set a new password
  • Because customers will always surprise you, you’ll need to make them validate their new password to be sure it will not be lost again
  • You can then clean the generated token





So how did we made that using Elixir/Phoenix in our application ? We used an awesome feature of Ecto, the database wrapper library provided with Elixir. It allows you to use a DSL in order to create migrations and SQL queries, thus allowing some features like logging model changes before saving them.

The get in depth knowledges about Ecto you should go check the hex documentation that is very good (one of the few sadly). So Ecto is divided in various submodules but we’re going to see only the ones we use on our project. Ecto.Repo is the tool that allows us to make queries to the datastore. It provides known function like Repo.[all, get, create, update, delete…] that you can use for simple queries. For more complicated ones, you need the Ecto.Query module to join, or subquery resources. The Ecto.Multi module allows you to use transactions to rollback complex queries. The Ecto.Migration allows you to write migrations with up and down functions allowing you to rollback a migration if necessary (feels safer). And Ecto.Changeset allows you to store all changes made to a model and/or validate those changes depending on specified constraints.

So let's see some of these constraints to make sure our customer do not bypass our beautiful authentication system. We define them in our User model as is.


defmodule MyApp.Account.User do
  use Ecto.Schema

  import Ecto.Changeset

  alias MyApp.Account.User

  @primary_key {:id, Ecto.UUID, autogenerate: true}

  @derive {Phoenix.Param, key: :id}

  schema "users" do
    field(:email, :string)

    field(:password, :string)

    field(:password_confirmation, :string, virtual: true)

    field(:token, :string)

    field(:reset_password_token, :string)

    field(:reset_token_sent_at, :utc_datetime)

    many_to_many(
      :roles,
      MyApp.Account.Role,
      join_through: "users_roles",
      on_delete: :delete_all,
      on_replace: :delete,
      unique: true
    )

    timestamps()
  end

  def changeset(%User{} = model, attrs \\ :empty) do
    model
    |> cast(attrs, [
      :email,
      :password,
      :password_confirmation,
      :token,
      :reset_password_token,
      :reset_token_sent_at
    ])
    |> unique_constraint(:email)
    |> validate_format(:email, ~r/@/)
    |> validate_required([:email])
  end

  def reset_password_changeset(%User{} = user, params \\ %{}) do
    user
    |> cast(params, [])
    |> put_reset_token()
  end

  defp put_reset_token(changeset) do
    token = SecureRandom.urlsafe_base64()

    sent_at = DateTime.utc_now()

    case changeset do
      %Ecto.Changeset{valid?: true} ->
        changeset
        |> put_change(:reset_password_token, token)
        |> put_change(:reset_token_sent_at, sent_at)

      _ ->
        changeset
    end
  end

  def update_password_changeset(%User{} = user, params \\ %{}) do
    user
    |> cast(params, [:password, :password_confirmation])
    |> validate_length(:password, min: 5)
    |> validate_required([:password, :password_confirmation])
    |> validate_confirmation(:password)
    |> put_pass_hash()
    |> clear_password_reset_token()
  end

  defp put_pass_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: password}} ->
        put_change(changeset, :password, Comeonin.Bcrypt.hashpwsalt(password))

      _ ->
        changeset
    end
  end

  defp clear_password_reset_token(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true} ->
        changeset
        |> put_change(:reset_password_token, nil)
        |> put_change(:reset_token_sent_at, nil)

      _ ->
        changeset
    end
  end
end

You can see the default changes function that will cast our attributes and validate the mandatory one like the email format or the uniqueness of the email. Note that we also modified default primary key format for our model. Ecto manage integer id but we wanted UUID, you can find more about this in the documentation.

We also define a reset_password_changeset/2 that will set the :reset_password_token and :reset_token_sent_at fields when the user ask for a new password.

These will allow us to check if the token is not too old and to verify that the provided token is correct to be sure that URL will be called only once.

On the update_password/2 controller function side we’ll just get the user based on his token and email, check that the token is less than one day and call the update_password_changeset/2 to put the new hashed password if the password_confirmation is correct of course.

As you can see, you can add complex constraints, custom constraints, on a unique or on multiple fields and also defined virtual fields that are not in the datastore (check carefully the model definition for password_confirmation) using Eco.Changeset. You can also add an explicit error if necessary using Ecto.Changeset.add_error/3.

The library make your code clean for model validation, but this is just the beginning as we are going to implement a change log system for our defined models thanks to the provided Ecto.Changeset in another post.

If you like the post please add a like or a comment so I can improve it as much as I can.

Elixir: Account Management

We'll begin our Elixir journey with the account management system. Stay tuned, more are comings :)

Our project use a PostgreSQL database as datastore. We do not need any temporary datastore for now, but if we needed to we could use Mnesia for temporary persisted data.

Our authentication system is totally based upon Elixir Guardian library. Uberauth that made the previous package, also provides another library allowing us to follow the OAuth 2 standard seamlessly. We also needed the JWT token management provided by the library for our mobile device clients.

So how did we implement it ? As shown in the quick tutorial Guardian offers some modules that we used. One is Guardian.Plug allowing us to plug the authentication and authorization system directly into Phoenix. Another is Guardian.DB allowing us to store emitted tokens in a database and invalidate them if necessary. The last one that we’ll use is Guardian.Permissions for roles based authorization system.

So let’s have a look to our router.ex :

use MyAppWeb, :router

pipeline :api do
  plug(:accepts, ["json", "json-api"])
end

pipeline :api_auth do
  plug(:accepts, ["json", "json-api"])

  plug(
    Guardian.Plug.Pipeline,
    module: MyApp.Account.Guardian,
    error_handler: MyApp.Account.AuthErrorHandler
  )

  plug(Guardian.Plug.VerifySession, claims: %{"typ" => "access"})

  plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"})

  plug(Guardian.Plug.EnsureAuthenticated)

  plug(Guardian.Plug.LoadResource)
end

pipeline :api_admin do
  plug(Guardian.Permissions.Bitwise, ensure: %{admin: [:all]})
end

pipeline :api_user do
  plug(Guardian.Permissions.Bitwise, ensure: %{user: [:all]})
end

pipeline :api_admin_or_user do
  plug(
    Guardian.Permissions.Bitwise,
    one_of: [
      %{admin: [:all]},
      %{user: [:all]}
    ]
  )
end

As you can see we defined 5 pipelines based on the required rights that our customers will need. First the :api is a simple pipeline for public routes like our health check URL. Next the :api_auth defines the protected pipeline based on our specific authentication system as of the error handler or how we should receive and parse the JWT Token. The next 3 other pipelines define the roles and actions available in our application. The last pipeline is interesting because it combines the preceding 2 roles so the user is part of the user group or the admin group.

Let’s have a closer look to our account management module that will manage our token and authorizations.

For that we’ll need to take a step back and understand how the token authentication works.




As you can see the token is generated after credentials validation (sign in) by the server. To check credentials we use a basic hash algorithm with salt so we store safely the user password. When we generate the token we also store the user role defined permissions in it so we can check in the router if the user does have the correct rights (actually this step is managed by Guardian).


defmodule MyApp.Account.Guardian do
  use Guardian,
    otp_app: :gi_api,
    permissions: %{
      default: [:all],
      admin: [:all],
      user: [:all]
    }

  use Guardian.Permissions.Bitwise

  import Ecto.Query

  alias MyApp.Repo

  alias MyApp.Account.User

  def subject_for_token(%{id: id}, _claims) do
    sub = to_string(id)

    {:ok, sub}
  end

  def subject_for_token(_, _) do
    {:error, :reason_for_error}
  end

  def resource_from_claims(%{"sub" => id}) do
    resource =
      User
      |> where(id: ^id)
      |> Repo.one!()

    {:ok, resource}
  end

  def resource_from_claims(_claims) do
    {:error, :reason_for_error}
  end

  def after_encode_and_sign(resource, claims, token, _options) do
    with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do
      {:ok, token}
    end
  end

  def on_verify(claims, token, _options) do
    with {:ok, _} <- Guardian.DB.on_verify(claims, token) do
      {:ok, claims}
    end
  end

  def on_revoke(claims, token, _options) do
    with {:ok, _} <- Guardian.DB.on_revoke(claims, token) do
      {:ok, claims}
    end
  end

  def build_claims(claims, _resource, opts) do
    claims =
      claims
      |> encode_permissions_into_claims!(Keyword.get(opts, :permissions))

    {:ok, claims}
  end
end

defmodule MyApp.Account.AuthErrorHandler do
  use MyAppWeb, :controller

  def auth_error(conn, {_type, _reason}, _opts) do
    conn
    |> put_resp_header("content-type", "application/json")
    |> put_status(:unauthorized)
    |> render(MyAppWeb.ErrorView, "401.json")
  end
end

The AuthErrorHandler will just show a 401 Unauthorized when unable to authenticate. We could add some Logging there for debug purposes as of the reason is provided. Guardian allows us to define some custom hooks in the module to manage token serialization and deserialization. So we used it to encode the use id in the token and to get it back from database on deserialization (subject_for_token/2 and resource_from_claims/1)

We also use the default handlers provided by Guardian.DB to store the token on sign in and remove it on sign out from our database. As you can see we have a pretty customizable authentication and authorization system, so let’s see how to use it.

Next we see the defined routes for the authentication system we’ll use. We followed the Uberauth recommendations for a local login.

scope "/auth", MyAppWeb do
  pipe_through([:api])

  post("/signup", AuthController, :signup)

  get("/:provider/callback", AuthController, :callback)

  post("/:provider/callback", AuthController, :callback)
end

scope "/auth", MyAppWeb do
  pipe_through([:api_auth])

  delete("/signout", AuthController, :signout)
end


So the only protected route here is the signout which will only call the Guardian.revoke/1 function that will execute the on_revoke/3 hook defined earlier in our Account.Guardian module.

Let’s have a look to our sign in internal code to see how we generate the token and store it in the connection struct returned to clients.


def sign_in(conn, %User{email: email}, password) do
  with {:ok, %User{} = user} <- get_by_email(email) do
    case authenticate(user, password) do
      true ->
        perms =
          Enum.reduce(user.roles, %{}, fn role, acc ->
            Map.put(acc, role.name, Guardian.max())
          end)

        auth_conn = Guardian.Plug.sign_in(conn, user, %{}, ttl: {1, :day}, permissions: perms)

        {:ok, auth_conn}

      _error ->
        {:error, :not_found}
    end
  end
end

defp authenticate(user, password) when not is_nil(user) do
  Comeonin.Bcrypt.checkpw(password, user.password)
end


This is how we store the user attached roles in the token with max permissions when we authenticate successfully based on the provided password. We could add some extra information in the token using the third argument of Guardian.Plug.sign_in/4.

Now that we have an authentication system we need to define our authentication protocol to work with any client. This is where OAuth 2 is useful because it’s a known standard for authentication and resources access. For reminding, there is a simplified diagram of how it works.



In our application to be able to manage various authentication providers as presented in the diagram, we need to have a route as access redirect URL, in our example it is named: /:provider/callback.

For our local signing feature we’ll use the default provider called identity which is used for a simple login, password sign in.


def callback(%{assigns: %{ueberauth_auth: auth}} = conn, params) do
  sign_in_user(conn, basic_info(auth), params)
end

defp basic_info(%Auth{} = auth) do
  %User{email: auth.info.email, password: auth.credentials.other.password}
end


The interesting part is how is structured the uberauth object that we get from the library. It contains an info field with the provided email and a credentials field with the password. Then we call our sign_in_user/3 function showed a little earlier to attach the token to the connection.

Annnd that’s it, you should have a working Authentication/Authorization manager following the principles of OAuth 2 protocol specification. You can go much further on the subject by looking to what is a refresh token and how to manage this in your client or searching more informations on how to implement other providers like Facebook or Google auth in your application.

Next we'll see how to add a Forgot Password feature using Elixir and Phoenix.

Most seen