Mass Assignment in Chi with Jwt Tokens
Mass Assignment in Chi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Mass assignment is a property-level authorization issue where an attacker can set fields that should be immutable or controlled by the server. In Chi, this often occurs when route parameters or body payloads are bound directly to a model or record without explicit filtering. When Jwt Tokens are involved, the risk intensifies because developers may trust claims embedded in the token to authorize updates, while also using those same claims to drive mass assignment logic.
For example, a Chi route that decodes a Jwt Token to extract a user identifier and then binds incoming JSON directly to an Ecto changeset can inadvertently allow an attacker to modify sensitive fields such as role, permissions, or admin status. If the changeset uses Ecto.Changeset.cast(params, ~w()a field)a list) with a broad field list derived from token claims or session data, an attacker who obtains or guesses a token can escalate privileges by assigning fields like is_admin or role.
Even when Jwt Tokens are validated and the subject or scopes are inspected, mass assignment may still occur if the application merges token-derived metadata with request parameters before passing them to the data layer. Chi does not automatically prevent this; it relies on the developer to define which fields are assignable per context. Without strict allowlists, an attacker can exploit overly permissive bindings to modify resources they should not access, leading to vertical or horizontal privilege escalation.
Real-world attack patterns include an authenticated user sending a PATCH request with an added is_superuser field, or including crafted claims in a Jwt Token that reference higher-privilege roles. When combined with weak parameter filtering, such requests can change ownership or permissions in the database. This maps to the OWASP API Top 10 API1:2023 – Broken Object Level Authorization and can violate compliance frameworks such as SOC 2 and GDPR when sensitive attributes are modified unintentionally.
Jwt Tokens-Specific Remediation in Chi — concrete code fixes
Remediation focuses on strict field allowlisting and keeping Jwt Token usage for authentication and authorization metadata separate from mass assignment inputs. Always bind only the fields you intend to update, and do not derive the assignable field list from token claims.
Example: Unsafe binding with Jwt Token claims
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
# UNSAFE: directly casting all keys from the body
def update(conn, %{"id" => id, "user" => user_params}) do
# Assume token claims include role — do NOT use them to build the field list
changeset = User.changeset(user, user_params) # vulnerable to mass assignment
# ... handle changeset
end
end
Example: Safe, explicit allowlist with Jwt Token metadata
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
import MyApp.Accounts, only: [update_user: 2]
# SAFE: explicit fields, ignoring any attacker-supplied fields
def update(conn, %{"id" => id, "user" => user_params}) do
# Decode Jwt Token early to get subject and scopes, but do not use for casting
with {:ok, claims} <- MyApp.Auth.verify_jwt(conn), # returns %{sub: user_id, scopes: [...]}
true <- claims["sub"] == id, # ensure user can only update themselves
changeset <- User.update_changeset(id, user_params) do # explicit function
# proceed with authorized update
end
end
end
Define your changeset with a strict list of permitted fields per context. Never use dynamic field lists derived from tokens or session data.
Safe changeset definition
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :username, :string
field :role, :string
# do NOT include :is_admin or :permissions in the default update form
timestamps()
end
# Public changeset for registration — limited fields
def registration_changeset(user, params) do
user
|> cast(params, [:email, :username])
|> validate_required([:email, :username])
end
# Admin-specific changeset — explicit allowlist
def admin_update_changeset(user, params) do
user
|> cast(params, [:email, :username, :role])
|> validate_required([:email, :username])
end
# Contextual update changeset used by controllers — explicit per action
def update_changeset(user_id, params) do
# Fetch user separately; do not rely on params for IDs
user = MyApp.Repo.get!(User, user_id)
user
|> cast(params, [:email, :username])
|> validate_required([:email, :username])
end
end
In your Chi pipeline, validate the Jwt Token and extract claims, but do not plug those claims into the casting step. Use them only for authorization decisions after the changeset is built. If you need role or scope checks, perform them explicitly before invoking the update function.
Chi pipeline example with token validation and safe binding
defmodule MyAppWeb.Router do
use MyAppWeb, :router
import MyAppWeb.AuthPlug, only: [require_jwt: 2]
pipeline :api do
plug :accepts, ["json"]
plug require_jwt
end
scope "/api", MyAppWeb do
pipe_through :api
patch "/users/:id", UserController, :update
end
end
defmodule MyAppWeb.AuthPlug do
import Plug.Conn
def require_jwt(conn, _opts) do
case extract_and_validate(conn) do
{:ok, claims} ->
# Attach claims for later use, not for mass assignment
assign(conn, :jwt_claims, claims)
{:error, _} ->
send_resp(conn, 401, "Unauthorized")
halt(conn)
end
end
defp extract_and_validate(conn) do
# Extract token from bearer header, validate, and return claims
# This is a placeholder for real Jwt handling logic
{:ok, %{sub: "user-123", scopes: ["update:own_profile"]}}
end
end
By keeping Jwt Token validation separate and using explicit allowlists in your changesets, you mitigate mass assignment risks even when tokens are present. This approach aligns with OWASP API Top 10 guidance and helps satisfy compliance requirements without over-relying on token metadata for data binding.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |