Secure API Design Patterns in Elixir: Authentication, Rate Limiting, and More

Explore advanced patterns for building secure APIs in Elixir, focusing on authentication, rate limiting, input filtering, and response headers. Enhance your API security with practical examples and best practices.

23.12. Patterns for Building Secure APIs

In today’s interconnected world, APIs are the backbone of modern applications, enabling communication between different software systems. However, with great power comes great responsibility. Ensuring the security of your APIs is crucial to protect sensitive data and maintain the integrity of your application. In this section, we will explore advanced patterns for building secure APIs in Elixir, focusing on key aspects such as authentication, rate limiting, input filtering, and response headers.

API Authentication

Authentication is the first line of defense in securing your API. It ensures that only authorized users can access your resources. Let’s delve into implementing token-based authentication in Elixir.

Implementing Token-Based Authentication

Token-based authentication is a popular method for securing APIs. It involves issuing a token to a user upon successful authentication, which is then used to access protected resources.

Key Steps:

  1. User Authentication: Verify the user’s credentials (e.g., username and password).
  2. Token Generation: Generate a token (e.g., JWT) upon successful authentication.
  3. Token Storage: Store the token securely on the client-side.
  4. Token Validation: Validate the token on each API request.

Code Example:

 1defmodule MyAppWeb.AuthController do
 2  use MyAppWeb, :controller
 3
 4  alias MyApp.Accounts
 5  alias MyApp.Guardian
 6
 7  def login(conn, %{"username" => username, "password" => password}) do
 8    case Accounts.authenticate_user(username, password) do
 9      {:ok, user} ->
10        {:ok, token, _claims} = Guardian.encode_and_sign(user)
11        json(conn, %{token: token})
12      {:error, reason} ->
13        conn
14        |> put_status(:unauthorized)
15        |> json(%{error: reason})
16    end
17  end
18end

Explanation:

  • User Authentication: The Accounts.authenticate_user/2 function verifies the user’s credentials.
  • Token Generation: The Guardian.encode_and_sign/1 function generates a JWT token for the authenticated user.
  • Response: The token is returned in the JSON response.

Try It Yourself:

  • Modify the code to include additional claims in the JWT.
  • Implement token expiration and refresh mechanisms.

Rate Limiting

Rate limiting is essential to prevent abuse and ensure fair usage of your API. It involves restricting the number of requests a client can make in a given time period.

Preventing Abuse with Throttling

Throttling helps protect your API from being overwhelmed by too many requests, which can lead to denial of service.

Key Steps:

  1. Identify Rate Limits: Determine the appropriate rate limits for your API.
  2. Implement Throttling Logic: Use a library or custom logic to enforce rate limits.
  3. Monitor and Adjust: Continuously monitor usage patterns and adjust limits as needed.

Code Example:

 1defmodule MyAppWeb.Plugs.RateLimiter do
 2  import Plug.Conn
 3
 4  @rate_limit 100
 5  @time_window :timer.minutes(1)
 6
 7  def init(default), do: default
 8
 9  def call(conn, _opts) do
10    client_ip = get_peer_data(conn).address |> Tuple.to_list() |> Enum.join(".")
11    key = "rate_limit:#{client_ip}"
12
13    case MyApp.Cache.get(key) do
14      nil ->
15        MyApp.Cache.put(key, 1, @time_window)
16        conn
17      count when count < @rate_limit ->
18        MyApp.Cache.increment(key)
19        conn
20      _ ->
21        conn
22        |> put_status(:too_many_requests)
23        |> json(%{error: "Rate limit exceeded"})
24        |> halt()
25    end
26  end
27end

Explanation:

  • Rate Limit: The @rate_limit variable defines the maximum number of requests allowed.
  • Time Window: The @time_window variable specifies the time period for the rate limit.
  • Throttling Logic: The call/2 function checks the request count for the client’s IP and enforces the rate limit.

Try It Yourself:

  • Experiment with different rate limits and time windows.
  • Implement user-specific rate limits based on authentication tokens.

Input Filtering

Input filtering is crucial to prevent malicious data from entering your system. It involves validating and sanitizing API inputs to ensure they meet expected criteria.

Validating and Sanitizing API Inputs

Proper input validation and sanitization can protect your API from common vulnerabilities such as SQL injection and cross-site scripting (XSS).

Key Steps:

  1. Define Validation Rules: Specify the expected format and constraints for each input parameter.
  2. Implement Validation Logic: Use a validation library or custom logic to enforce rules.
  3. Sanitize Inputs: Remove or escape any potentially harmful characters.

Code Example:

 1defmodule MyAppWeb.UserController do
 2  use MyAppWeb, :controller
 3
 4  alias MyApp.Accounts
 5  alias MyAppWeb.UserView
 6
 7  def create(conn, %{"user" => user_params}) do
 8    changeset = Accounts.change_user(%User{}, user_params)
 9
10    if changeset.valid? do
11      {:ok, user} = Accounts.create_user(user_params)
12      conn
13      |> put_status(:created)
14      |> render(UserView, "show.json", user: user)
15    else
16      conn
17      |> put_status(:unprocessable_entity)
18      |> render(MyAppWeb.ChangesetView, "error.json", changeset: changeset)
19    end
20  end
21end

Explanation:

  • Validation Rules: The Accounts.change_user/2 function applies validation rules to the input parameters.
  • Sanitization: The changeset.valid? check ensures that only valid data is processed.

Try It Yourself:

  • Add custom validation rules for specific fields.
  • Implement input sanitization for HTML content.

Response Headers

Setting security-related response headers can enhance the security of your API by controlling how browsers handle your content.

Security headers such as CORS (Cross-Origin Resource Sharing) and CSP (Content Security Policy) can protect your API from various attacks.

Key Steps:

  1. Identify Required Headers: Determine which security headers are necessary for your API.
  2. Implement Header Logic: Use a plug or middleware to set headers on each response.
  3. Test and Monitor: Ensure headers are correctly set and monitor for any issues.

Code Example:

 1defmodule MyAppWeb.Plugs.SecurityHeaders do
 2  import Plug.Conn
 3
 4  def init(default), do: default
 5
 6  def call(conn, _opts) do
 7    conn
 8    |> put_resp_header("x-content-type-options", "nosniff")
 9    |> put_resp_header("x-frame-options", "DENY")
10    |> put_resp_header("x-xss-protection", "1; mode=block")
11    |> put_resp_header("content-security-policy", "default-src 'self'")
12  end
13end

Explanation:

  • Security Headers: The put_resp_header/3 function sets various security headers on the response.
  • CSP: The content-security-policy header restricts the sources from which content can be loaded.

Try It Yourself:

  • Experiment with different CSP policies.
  • Add additional security headers as needed.

Visualizing API Security Patterns

To better understand the flow of securing an API, let’s visualize the process using a sequence diagram.

    sequenceDiagram
	    participant Client
	    participant API
	    participant AuthService
	    participant RateLimiter
	    participant Validator
	    participant SecurityHeaders
	
	    Client->>API: Request with credentials
	    API->>AuthService: Authenticate user
	    AuthService-->>API: Return token
	    API-->>Client: Respond with token
	
	    Client->>API: Request with token
	    API->>RateLimiter: Check rate limit
	    RateLimiter-->>API: Allow request
	    API->>Validator: Validate input
	    Validator-->>API: Valid input
	    API->>SecurityHeaders: Set headers
	    SecurityHeaders-->>API: Headers set
	    API-->>Client: Respond with data

Diagram Explanation:

  • Authentication: The client sends credentials to the API, which authenticates the user and returns a token.
  • Rate Limiting: The API checks the rate limit before processing the request.
  • Input Validation: The API validates the input to ensure it meets the expected criteria.
  • Security Headers: The API sets security-related headers before responding to the client.

Knowledge Check

  • What are the key components of token-based authentication?
  • How can rate limiting protect your API from abuse?
  • Why is input validation important for API security?
  • What role do security headers play in protecting your API?

Embrace the Journey

Remember, building secure APIs is an ongoing process. As you implement these patterns, continue to monitor and adapt your security measures to address new threats. Keep experimenting, stay curious, and enjoy the journey of creating robust and secure APIs!

Quiz: Patterns for Building Secure APIs

Loading quiz…
Revised on Thursday, April 23, 2026