Security in Elixir and Phoenix
Security is a fundamental aspect of web development that should never be overlooked.
With the increasing number of cyber threats, developers must adopt strong security practices to safeguard applications from malicious attacks. Data breaches, unauthorized access, and system vulnerabilities can have severe consequences, including financial loss, legal implications, and reputational damage.
To build secure web applications, developers must understand common threats such as SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF). Secure coding practices, encryption, and authentication mechanisms play a crucial role in mitigating these risks. Additionally, adopting a proactive security mindset—regular code audits, dependency management, and staying updated with security advisories—can help prevent potential exploits before they occur.
When developing applications, leveraging frameworks and languages that emphasize security can make a significant difference. In this article, we will explore key security concerns in web applications, particularly within the context of Elixir language and Phoenix, and provide practical solutions to mitigate vulnerabilities.
Some of the security topics (Mass assignment vulnerability, Dynamic atom creation, SQL injection, XSS attacks) I already cover in my different article here: https://curiosum.com/blog/elixir-anti-patterns. I encourage you to take a look at that article because from now on I will no longer refer to these topics in this article. In this article I will focus on a few selected topics related to security in Elixir and Phoenix.
Table of contents
- Session Security in Phoenix
- CSRF Protection in Phoenix
- Rate Limiting and Throttling
- Dependency Management and Security Updates
- Authentication & Authorization in Elixir and Phoenix
- Sobelow library
Session Security in Phoenix
Sessions are a fundamental aspect of web applications, enabling user authentication and maintaining state across requests. However, improperly handled sessions can become a target for attackers, leading to session hijacking, fixation, and unauthorized access. Securing sessions in Phoenix helps prevent these risks and ensures user data integrity.
Configuring Secure Sessions in Phoenix
Enabling Session Encryption
By default, Phoenix stores session data in signed, but not encrypted, cookies. Configuring sessions securely is crucial to protect against potential attacks. To enhance security, enable encryption by setting encryption_salt in the session configuration:
plug Plug.Session,
store: :cookie,
key: "_my_app_session",
signing_salt: "random_salt",
encryption_salt: "another_salt"
This encryption_salt
option ensures that session data is encrypted, preventing attackers and users from reading the contents of cookies. User would not be able to decrypt session cookie and read it's data.
Set the :secure flag
When using HTTPS, always ensure that the session cookies are only sent over secure connections. This can be achieved by setting the :secure flag in your session cookie configuration.
plug Plug.Session,
store: :cookie,
key: "_my_app_session",
signing_salt: "random_salt",
secure: true # Ensures the cookie is sent only over HTTPS
Set the :http_only flag
This flag prevents the cookie from being accessible via JavaScript, protecting against cross-site scripting (XSS) attacks.
plug Plug.Session,
store: :cookie,
key: "_my_app_session",
signing_salt: "random_salt",
http_only: true # Prevents JavaScript access to the cookie
When http_only: true is set then:
- the cookie cannot be accessed by JavaScript (document.cookie will not expose it),
- It reduces the risk of client-side script attacks like Cross-Site Scripting (XSS) stealing session tokens.
Use :same_site flag
This flag helps mitigate Cross-Site Request Forgery (CSRF) by limiting when cookies are sent. Setting :same_site to :strict or :lax restricts cookies from being sent along with cross-site requests. By default this flag is set to :lax. If you need more security you can set it to :strict.
plug Plug.Session,
store: :cookie,
key: "_my_app_session",
signing_salt: "random_salt",
same_site: :lax # Adds CSRF protection
Session expiration
Session expiration plays a significant role in maintaining session security. By default, Phoenix sessions don’t have an expiration time, which could leave an open window for attackers if they gain access to a session. It’s crucial to implement a session timeout strategy. You can configure session expiration by setting a :max_age parameter. This ensures the session is valid only for a certain duration before requiring re-authentication.
plug Plug.Session,
store: :cookie, key: "_my_app_session",
signing_salt: "random_salt",
max_age: 86400 # Session expires after one day (in seconds)
Session management
One additional consideration is how session data is stored and managed. Phoenix allows the use of in-memory, cookie-based, or database-backed session stores. While cookie-based sessions are more efficient, in certain cases, such as when you need to track longer sessions or store more data, you might opt for a database-backed session store.
- In-memory stores are ideal for small-scale applications but may not scale well.
- Cookie-based stores are good for stateless applications but may require encryption for sensitive data.
- Database-backed stores are more secure for critical data but introduce a slight performance overhead.
You can store your session in cookie (default setting for Phoenix) or in an in-memory ETS table.
CSRF Protection in Phoenix
Cross-Site Request Forgery (CSRF) is a common web security vulnerability that allows attackers to trick authenticated users into making unwanted requests to a web application. Phoenix, as a secure web framework, provides built-in CSRF protection to safeguard against these attacks.
How CSRF Attacks Work
CSRF attacks exploit the trust a web application has in an authenticated user's browser. An attacker tricks a victim into submitting a malicious request by embedding a request in a link, form, or script that executes automatically. Since the request originates from the victim's browser, it carries the user's session credentials, making it appear legitimate.
CSRF Protection in Phoenix
Phoenix provides CSRF protection by leveraging CSRF tokens, which ensure that requests are intentionally made by the user. Phoenix automatically includes a CSRF token in all forms generated with the Phoenix.Component.form/1 component. This token is required for form submissions and validated by the framework.
<.form :let={f} for={@changeset} action={~p"/comments/#{@comment}"}>
<.input field={f[:body]} />
</.form>
When Phoenix receives a request that modifies data (e.g., POST, PUT, DELETE), it verifies the CSRF token. If the token is missing or invalid, Phoenix rejects the request. To fully protect your app against CSRF attacks you also need to add :protect_from_forgery plug to your router.ex.
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {MyApp.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
Now your app is validating CSRF token on every request that requires token.
CSRF Token in JavaScript Requests
In modern web applications, a lot of interactions occur via JavaScript, such as AJAX requests. If you are submitting data asynchronously (e.g., with Fetch or Axios), you need to manually include the CSRF token in the request headers. Phoenix makes this easy by storing the CSRF token in a meta tag:
<meta name="csrf-token" content={get_csrf_token()} />
You can then access this token in your JavaScript and attach it to the headers of any request, ensuring protection even in AJAX-based interactions.
const csrfToken = document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content')
fetch('/some/endpoint', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken},
body: JSON.stringify({ key: 'value' })
})
CSRF Protection for Delete Links
When creating DELETE actions in Phoenix, it's important to ensure CSRF protection is enforced, even when triggering deletions from links ( elements). Since standard HTML links do not support the DELETE HTTP method, Phoenix provides a way to safely handle these actions while ensuring CSRF protection.
<.link href="/hello" method="delete" data-confirm="Really?">delete</.link>
In case the method is not get, the link is generated inside the form which sets the proper information. In order to submit the form, JavaScript must be enabled in the browser.
Rate Limiting and Throttling
Rate limiting and throttling are essential security mechanisms in Rate limiting and throttling are essential security mechanisms in web applications, preventing abuse, mitigating denial-of-service (DoS) attacks, and ensuring fair usage of system resources. In Elixir and Phoenix, these mechanisms can be implemented using various strategies and tools.
Why Use Rate Limiting?
Rate limiting helps:
- Prevent brute-force attacks by limiting the number of login attempts.
- Reduce the risk of API abuse by restricting excessive requests.
- Protect server resources by avoiding traffic spikes that degrade performance.
- Ensure fair usage among users.
Implementing Rate Limiting in Phoenix
A common way to implement rate limiting in Phoenix is by using the Hammer library, which provides a simple way to define rate-limiting rules. Also for more advanced rate limiting with distributed storage, Hammer is a great choice. It supports backends like ETS and Redis, making it scalable for high-traffic applications. Here is simple example how to create basic rate limiter plug in your app.
Add hammer to your dependencies:
{:hammer, "~> 7.0"}
Define a rate limiter module in your application. Use the Hammer module with your chosen backend and configure options as needed:
defmodule MyAppWeb.RateLimit do
@moduledoc false
use Hammer, backend: :ets
end
Add the rate limiter to your application's supervision tree or start it manually by calling MyApp.RateLimit.start_link/1 with any runtime options:
MyApp.RateLimit.start_link(clean_period: :timer.minutes(1))
Then configure and use it in a plug:
defmodule MyAppWeb.RateLimiter do
@moduledoc false
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
case MyAppWeb.RateLimit.hit(conn.remote_ip, 60_000, 10) do
{:allow, _count} -> conn
{:deny, _count} -> conn |> send_resp(429, "Too Many Requests") |> halt()
end
end
end
This configuration limits each IP address to 10 requests per minute.
Next use your plug wherever you need. You can add it to your router browser pipeline.
plug(MyAppWeb.RateLimiter)
For more information about rate limiting using Hammer library check it's official docs!
Dependency Management and Security Updates
Managing dependencies securely is a critical aspect of building robust Elixir and Phoenix applications. Outdated or vulnerable dependencies can introduce security risks, making regular updates and careful package selection is essential.
Auditing Dependencies for Security Vulnerabilities
Mix provides a mix hex.audit task. It will show you all Hex dependencies that have been marked as retired. It means that package is no longer recommended to be used by their maintainers. To check for known retired packages in your dependencies, use:
mix hex.audit
This task is good but it checks only for retired packages. If we want to check for security vulnerabilities we might want to introduce mix_audit library.
To add mix_audit library to your project you can simply add it to your deps:
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}
and then to check for security vulnerabilities in your dependencies, use:
mix deps.audit
This will scan dependencies against known vulnerabilities and provide recommendations.
Both commands mix hex.audit and mix deps.audit you can add to your CI pipeline. Check out our previous blog post on this topic: https://curiosum.com/blog/mastering-elixir-ci-pipeline and also check our Elixir meetup for more: https://curiosum.com/blog/optimizing-elixir-ci-pipelines about setting up your CI pipeline in Elixir.
Authentication & Authorization in Elixir and Phoenix
When building secure applications with Elixir and Phoenix, authentication and authorization are two fundamental concerns. Authentication ensures that users are who they claim to be, while authorization defines what actions they are allowed to perform within the system. Let's explore best practices and tools available for implementing these security measures.
Authentication is the process of verifying a user’s identity. In Phoenix applications, this is typically done by validating credentials during login and then maintaining a session or issuing a token for subsequent requests. Two common approaches are:
- Session-based authentication: Traditional and well-suited for browser-based applications. Sessions are stored on the server (or in encrypted cookies) and validated on each request.
- Token-based authentication: Often implemented using JSON Web Tokens (JWTs). This is useful for APIs and distributed systems where stateless communication is preferred.
Popular practices to implement authentication in Elixir:
- Guardian: A powerful JWT-based authentication library that integrates with the Plug pipeline. Guardian helps manage token creation, validation, and resource loading.
- Pow: A modular and extendable authentication solution that simplifies session management and user registration.
- Using phx.gen.auth: Since Phoenix 1.6, the built-in phx.gen.auth generator offers a robust, out-of-the-box authentication system that includes user registration, login, session management, password resets, and email confirmation.
Once a user is authenticated, the next step is authorization—determining whether the user has the necessary permissions to access a resource or perform an action.
Authorization is often implemented via:
- Role-based Access Control (RBAC): Where users have roles (e.g., admin, user) and each role has a set of permissions.
- Policy-based Authorization: Using more granular policies that decide if a particular action is permitted based on the context.
Sobelow Library
As I mention in my previous blog post here: https://curiosum.com/blog/elixir-anti-patterns Sobelow library will help you prevent a lot of security vulnerabilities in your Elixir app. List of checks that Sobelow can perform:
- Insecure configuration
- Known-vulnerable Dependencies
- Cross-Site Scripting
- SQL injection
- Command injection
- Code execution
- Denial of Service
- Directory traversal
- Unsafe serialization