Elixir Functions

 

Implementing Authentication in Elixir Applications

In today’s digital landscape, security is a paramount concern for any software application. Whether it’s a simple web app or a complex backend system, ensuring that only authorized users can access sensitive data and perform certain actions is crucial. This is where authentication comes into play. In this blog post, we will delve into the world of authentication in Elixir applications, exploring various strategies, best practices, and code samples to implement effective authentication mechanisms.

Implementing Authentication in Elixir Applications

1. Introduction to Authentication in Elixir

1.1. What is Authentication?

Authentication is the process of verifying the identity of a user, ensuring they are who they claim to be. It involves validating the user’s credentials, such as usernames and passwords, before granting access to the system.

1.2. Why is Authentication Important?

Authentication is a fundamental pillar of security in software applications. It prevents unauthorized access, protects sensitive user data, and ensures that actions within the system are performed by legitimate users. Without proper authentication, applications are vulnerable to data breaches, unauthorized transactions, and other security risks.

2. Common Authentication Strategies

2.1. Token-Based Authentication

Token-based authentication involves the use of tokens, typically in the form of JSON Web Tokens (JWT), to validate the identity of users. These tokens are generated upon successful login and are included in subsequent requests to authenticate the user.

2.2. Session-Based Authentication

Session-based authentication uses server-side sessions to manage user authentication. A session is established when a user logs in, and a unique session identifier is stored on the server and usually in a browser cookie. The session is checked on each request to ensure the user is authenticated.

2.3. OAuth and OpenID Connect

OAuth and OpenID Connect are protocols used for delegated authorization and single sign-on (SSO). OAuth allows third-party applications to access resources on behalf of a user, while OpenID Connect extends OAuth to provide authentication as well as authorization.

3. Building Authentication from Scratch

3.1. Creating User Models

In Elixir, user models can be defined using Ecto schemas. These schemas capture user-related information such as email and password hashes.

elixir
defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :email, :string
    field :password_hash, :string
    timestamps()
  end
end

3.2. Hashing Passwords

Storing plain-text passwords is a significant security risk. Instead, passwords should be hashed using a strong cryptographic algorithm like bcrypt. The Comeonin library provides hashing utilities for Elixir.

elixir
defmodule MyApp.SessionController do
  # ...

  def create(conn, %{"email" => email, "password" => password}) do
    user = Repo.get_by(User, email: email)

    if user && Comeonin.Bcrypt.checkpw(password, user.password_hash) do
      token = Phoenix.Token.sign(MyApp.Endpoint, "user", user.id)
      conn
      |> put_session(:current_user, user.id)
      |> json(%{token: token})
    else
      conn
      |> send_resp(401, "Unauthorized")
    end
  end
end

4. Using Third-Party Libraries

4.1. Guardian

Guardian is a popular authentication library for Elixir applications. It provides token-based authentication and JWT generation and verification. Guardian’s flexibility makes it suitable for various authentication scenarios.

4.2. Coherence

Coherence is a full-featured authentication and user management library for Phoenix applications. It offers features like email confirmation, password reset, and role-based authorization out of the box.

5. Best Practices for Authentication

5.1. Salted Hashing

Salting involves adding a random value (salt) to a password before hashing it. This adds an extra layer of security, making it harder for attackers to use precomputed tables (rainbow tables) to crack passwords.

5.2. Two-Factor Authentication

Implementing two-factor authentication (2FA) adds an extra layer of security by requiring users to provide a second factor, such as a code from their mobile device, in addition to their password.

5.3. Role-Based Authorization

In addition to authentication, role-based authorization defines what actions different users can perform within the application. This prevents unauthorized users from accessing certain features or data.

6. Implementing Token-Based Authentication

6.1. Generating Tokens

Using Guardian, you can generate JWT tokens upon successful authentication.

elixir
defmodule MyApp.GuardianSerializer do
  def for_token(user, _claims) do
    {:ok, "user:#{user.id}"}
  end

  def from_token("user:" <> id, _claims) do
    {:ok, %MyApp.User{id: id}}
  end
end

elixir
defmodule MyApp.SessionController do
  use MyApp.Web, :controller

  def create(conn, %{"email" => email, "password" => password}) do
    user = Repo.get_by(User, email: email)

    if user && Comeonin.Bcrypt.checkpw(password, user.password_hash) do
      {:ok, token, _claims} = Guardian.encode_and_sign(user, MyApp.GuardianSerializer)
      conn
      |> put_status(:created)
      |> json(%{token: token})
    else
      conn
      |> send_resp(401, "Unauthorized")
    end
  end
end

6.2. Authenticating Requests

Guardian provides a plug to authenticate requests using JWT tokens.

elixir
defmodule MyApp.AuthPlug do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    case Guardian.decode_and_verify(conn.private.guardian_default_claims) do
      {:ok, token_claims} ->
        user_id = String.split(List.first(token_claims), ":") |> List.last
        user = Repo.get(User, user_id)

        assign(conn, :current_user, user)

      {:error, _reason} ->
        conn
        |> send_resp(401, "Unauthorized")
        |> halt()
    end
  end
end

6.3. Token Expiry and Refresh

JWT tokens can be configured with expiration times. You can implement token refresh by issuing a new token when the old one is about to expire.

7. Integrating OAuth for Third-Party Logins

7.1. Setting Up OAuth Providers

You can use libraries like Ueberauth to integrate OAuth providers like Google or Facebook for authentication.

7.2. Handling Callbacks

After a user successfully logs in via OAuth, the OAuth provider will redirect them back to your application. You need to handle this callback and create or authenticate the user in your system.

7.3. Storing OAuth Tokens

OAuth tokens provided by the third-party service should be securely stored in your application’s database to facilitate future interactions with the service on behalf of the user.

8. Session Management and Security

8.1. Session Storage

Phoenix provides mechanisms to manage user sessions securely, including setting expiration times and encrypting session data.

8.2. Cross-Site Scripting (XSS) Protection

To prevent XSS attacks, sanitize and escape user input before rendering it in views.

8.3. Cross-Site Request Forgery (CSRF) Prevention

Implement CSRF protection to ensure that requests made to your application come from legitimate sources.

9. Protecting Sensitive Routes with Middleware

9.1. Custom Middleware in Elixir

You can create custom plugs to enforce authentication on specific routes.

elixir
defmodule MyApp.AuthRequired do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    case get_session(conn, :current_user) do
      user_id when not is_nil(user_id) ->
        conn

      _ ->
        conn
        |> send_resp(401, "Unauthorized")
        |> halt()
    end
  end
end

9.2. Enforcing Authentication Rules

Apply your custom authentication plugs to routes that require authentication.

10. Monitoring and Logging for Security

10.1. Logging Authentication Attempts

Implement logging mechanisms to keep track of successful and failed authentication attempts.

10.2. Monitoring for Suspicious Activities

Set up monitoring tools to detect and respond to suspicious activities, such as multiple failed login attempts from the same IP address.

Conclusion

In conclusion, implementing robust authentication mechanisms in Elixir applications is crucial for maintaining the security and integrity of your software. By understanding the various authentication strategies, leveraging third-party libraries like Guardian and Coherence, and following best practices, you can build a secure authentication system that protects user data and ensures only authorized users can access your application. Remember that security is an ongoing process, so stay informed about the latest security trends and updates to keep your application’s authentication mechanisms up to date and resilient against evolving threats.

Previously at
Flag Argentina
Brazil
time icon
GMT-3
Tech Lead in Elixir with 3 years' experience. Passionate about Elixir/Phoenix and React Native. Full Stack Engineer, Event Organizer, Systems Analyst, Mobile Developer.