Plug.CSRFProtection
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 token stored in the session with the one sent by the request to determine the validity of the request. For an invalid request the action taken is based on the :with
option.
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).
Note that it is recommended to enable CSRFProtection whenever a session is used, even for JSON requests. For example, Chrome had a bug that allowed POST requests to be triggered with arbitrary content-type, making JSON exploitable. More info: https://bugs.chromium.org/p/chromium/issues/detail?id=490015
Finally, we recommend developers to invoke delete_csrf_token/0
every time after they log a user in, to avoid CSRF fixation attacks.
Token generation
This plug won't generate tokens automatically. Instead, tokens will be generated only when required by calling get_csrf_token/0
. In case you are generating the token for certain specific URL, you should use get_csrf_token_for/1
as that will avoid tokens from being leaked to other applications.
Once a token is generated, it is cached in the process dictionary. The CSRF token is usually generated inside forms which may be isolated from Plug.Conn
. Storing them in the process dictionary allows them to be generated as a side-effect only when necessary, becoming one of those rare situations where using the process dictionary is useful.
Cross-host protection
If you are sending data to a full URI, such as //subdomain.host.com/path
or //external.com/path
, instead of a simple path such as /path
, you may want to consider using get_csrf_token_for/1
, as that will encode the host in the CSRF token. Once received, Plug will only consider the CSRF token to be valid if the host
encoded in the token is the same as the one in conn.host
.
Therefore, if you get a warning that the host does not match, it is either because someone is attempting to steal CSRF tokens or because you have a misconfigured host configuration.
For example, if you are running your application behind a proxy, the browser will send a request to the proxy with www.example.com
but the proxy will request you using an internal IP. In such cases, it is common for proxies to attach information such as "x-forwarded-host"
that contains the original host.
This may also happen on redirects. If you have a POST request to foo.example.com
that redirects to bar.example.com
with status 307, the token will contain a different host than the one in the request.
You can pass the :allow_hosts
option to control any host that you may want to allow. The values in :allow_hosts
may either be a full host name or a host suffix. For example: ["www.example.com", ".subdomain.example.com"]
will allow the exact host of "www.example.com"
and any host that ends with ".subdomain.example.com"
.
Options
-
:session_key
- the name of the key in session to store the token under -
:allow_hosts
- a list with hosts to allow on cross-host tokens -
:with
- should be one of:exception
or:clear_session
. Defaults to:exception
.-
:exception
- for invalid requests, this plug will raisePlug.CSRFProtection.InvalidCSRFTokenError
. -
:clear_session
- for invalid requests, this plug will set an empty session for only this request. Also any changes to the session during this request will be ignored.
-
Disabling
You may disable this plug by doing Plug.Conn.put_private(conn, :plug_skip_csrf_protection, true)
. This was made available for disabling Plug.CSRFProtection
in tests and not for dynamically skipping Plug.CSRFProtection
in production code. If you want specific routes to skip Plug.CSRFProtection
, then use a different stack of plugs for that route that does not include Plug.CSRFProtection
.
Examples
plug Plug.Session, ... plug :fetch_session plug Plug.CSRFProtection
Summary
Functions
- call(conn, arg)
Callback implementation for
Plug.call/2
.- delete_csrf_token()
Deletes the CSRF token from the process dictionary.
- dump_state()
Dump CSRF state so it can be loaded in another process.
- dump_state_from_session(session_token)
Dumps the CSRF state from the connection.
- get_csrf_token()
Gets the CSRF token.
- get_csrf_token_for(url)
Gets the CSRF token for the associated URL (as a string or a URI struct).
- init(opts)
Callback implementation for
Plug.init/1
.- load_state(secret_key_base, csrf_state)
Load CSRF state into the process dictionary.
- valid_state_and_csrf_token?(state, csrf_token)
Validates the
state
is valid against the givencsrf_token
.
Functions
call(conn, arg)
Callback implementation for Plug.call/2
.
delete_csrf_token()
Deletes the CSRF token from the process dictionary.
This will force the token to be deleted once the response is sent.
dump_state()
Dump CSRF state so it can be loaded in another process.
This function must be called after the Plug.CSRFProtection
plug is invoked. If a token was not yet computed, it will be.
See load_state/2
for more information.
dump_state_from_session(session_token)
Dumps the CSRF state from the connection.
It expects the value of get_session(conn, "_csrf_token")
. It returns nil
if there is no state in the session.
get_csrf_token()
Gets the CSRF token.
Generates a token and stores it in the process dictionary if one does not exist.
get_csrf_token_for(url)
Gets the CSRF token for the associated URL (as a string or a URI struct).
If the URL has a host, a CSRF token that is tied to that host will be generated. If it is a relative path URL, a simple token emitted with get_csrf_token/0
will be used.
init(opts)
Callback implementation for Plug.init/1
.
load_state(secret_key_base, csrf_state)
Load CSRF state into the process dictionary.
This can be used to load CSRF state into another process. See dump_state/0
and dump_state_from_session/2
for dumping it.
Examples
To dump the state from the current process and load into another one:
csrf_state = Plug.CSRFProtection.dump_state() secret_key_base = conn.secret_key_base Task.async(fn -> Plug.CSRFProtection.load_state(secret_key_base, csrf_state) end)
If you have a session but the CSRF state was not loaded into the current process, you can dump the state from the session:
csrf_state = Plug.CSRFProtection.dump_state_from_session(session["_csrf_token"]) Task.async(fn -> Plug.CSRFProtection.load_state(secret_key_base, csrf_state) end)
valid_state_and_csrf_token?(state, csrf_token)
Validates the state
is valid against the given csrf_token
.
© 2013 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/plug/Plug.CSRFProtection.html