Packages
plug
0.12.0
1.20.3
1.20.2
1.20.1
retired
1.20.0
retired
1.19.5
1.19.4
1.19.3
1.19.2
1.19.1
1.19.0
1.18.5
1.18.4
1.18.3
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.6
1.16.5
1.16.4
1.16.3
1.16.2
1.16.1
1.16.0
1.15.6
1.15.5
1.15.4
1.15.3
1.15.2
1.15.1
1.15.0
1.14.2
1.14.1
1.14.0
1.13.6
1.13.5
1.13.4
1.13.3
1.13.2
1.13.1
retired
1.13.0
retired
1.12.1
1.12.0
1.11.1
1.11.0
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.0
1.8.3
1.8.2
1.8.1
1.8.0
1.7.2
1.7.1
1.7.0
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.5.0-rc.2
1.5.0-rc.1
1.5.0-rc.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.0
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.14.0
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
Compose web applications with functions
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/plug/csrf_protection.ex
defmodule Plug.CSRFProtection do
@moduledoc """
Plug to protect from cross-site request forgery.
For this plug to work, it expects a session to have been
previously fetched. It will then compare the plug stored
in the session with the one sent by the request, when they
do not match, an `Plug.CSRFProtection.InvalidCSRFTokenError`
error is raised.
The token may be sent by the request either via the params
with key "_csrf_token" or a header with name "x-csrf-token".
GET requests are not protected, as they should not have any
side-effect or change your application state. JavaScript
requests are an exception: by using a script tag, external
websites can embed server-side generated JavaScript, which
can leak information. For this reason, this plug also forbids
any GET JavaScript request that is not XHR (or AJAX).
## Token generation
This plug won't generate tokens automatically. Instead,
tokens will be generated only when required by calling
`Plug.CSRFProtection.get_csrf_token/0`. The token is then
stored in the process dictionary to be set in the request.
One may wonder: why the process dictionary?
The CSRF token is usually generated inside forms which may
be isolated from the connection. Storing them in process
dictionary allow them to be generated as a side-effect,
becoming one of those rare situations where using the process
dictionary is useful.
## Disabling
You may disable this plug by doing
`Plug.Conn.put_private(:plug_skip_csrf_protection, true)`.
## Examples
plug Plug.Session, ...
plug :fetch_session
plug Plug.CSRFProtection
"""
import Plug.Conn
@unprotected_methods ~w(HEAD GET OPTIONS)
defmodule InvalidCSRFTokenError do
@moduledoc "Error raised when CSRF token is invalid."
message =
"invalid CSRF (Cross Site Forgery Protection) token, make sure all "
<> "requests include a '_csrf_token' param or an 'x-csrf-token' header"
defexception message: message, plug_status: 403
end
defmodule InvalidCrossOriginRequestError do
@moduledoc "Error raised when non-XHR requests are used for Javascript responses."
message =
"security warning: an embedded <script> tag on another site requested "
<> "protected JavaScript (if you know what you're doing, disable forgery "
<> "protection for this route)"
defexception message: message, plug_status: 403
end
## API
@doc """
Gets the CSRF token.
Generates a token and stores it in the process
dictionary if one does not exists.
"""
def get_csrf_token do
if token = Process.get(:plug_csrf_token) do
token
else
token = generate_token()
Process.put(:plug_csrf_token, token)
token
end
end
@doc """
Deletes the CSRF token from the process dictionary.
This will force the token to be deleted once the response is sent.
"""
def delete_csrf_token do
Process.delete(:plug_csrf_token)
end
## Plug
@behaviour Plug
def init(opts), do: opts
def call(conn, _opts) do
csrf_token = get_session(conn, "_csrf_token")
Process.put(:plug_csrf_token, csrf_token)
if not verified_request?(conn, csrf_token) do
raise InvalidCSRFTokenError
end
register_before_send(conn, &ensure_same_origin_and_csrf_token!(&1, csrf_token))
end
## Verification
defp verified_request?(conn, csrf_token) do
conn.method in @unprotected_methods
|| valid_csrf_token?(csrf_token, conn.params["_csrf_token"])
|| valid_csrf_token?(csrf_token, get_req_header(conn, "x-csrf-token") |> List.first)
|| skip_csrf_protection?(conn)
end
defp valid_csrf_token?(csrf_token, user_token)
when is_nil(csrf_token) or is_nil(user_token),
do: false
defp valid_csrf_token?(csrf_token, user_token),
do: Plug.Crypto.secure_compare(csrf_token, user_token)
## Before send
defp ensure_same_origin_and_csrf_token!(conn, csrf_token) do
if cross_origin_js?(conn) do
raise InvalidCrossOriginRequestError
end
ensure_csrf_token(conn, csrf_token)
end
defp cross_origin_js?(%Plug.Conn{method: "GET"} = conn),
do: not skip_csrf_protection?(conn) and not xhr?(conn) and js_content_type?(conn)
defp cross_origin_js?(%Plug.Conn{}),
do: false
defp js_content_type?(conn) do
conn
|> get_resp_header("content-type")
|> Enum.any?(&String.starts_with?(&1, ~w(text/javascript application/javascript)))
end
defp xhr?(conn) do
"XMLHttpRequest" in get_req_header(conn, "x-requested-with")
end
defp ensure_csrf_token(conn, csrf_token) do
case Process.delete(:plug_csrf_token) do
^csrf_token -> conn
current -> put_session(conn, "_csrf_token", current)
end
end
## Helpers
defp skip_csrf_protection?(%Plug.Conn{private: %{plug_skip_csrf_protection: true}}), do: true
defp skip_csrf_protection?(%Plug.Conn{}), do: false
defp generate_token do
:crypto.strong_rand_bytes(32) |> Base.encode64
end
end