Mix.install([:phoenix, :plug])
alias Phoenix.ConnTest
alias Plug
conn = ConnTest.build_conn()
:ok
An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to a user's web browser via HTTP headers or set via JavaScript. The browser may store the cookie and send it back to the same server with later requests.
Cookies are mainly used for three purposes:
- Session management - Logins, shopping carts, game scores, or anything else the server should remember
- Personalization - User preferences, themes, and other settings
- Tracking - Recording and analyzing user behavior
HTTP Cookies have attributes that influence how they are managed by the client(s) that consumes them. It is through the values set within these attributes that makes or breaks their security.
Cookies with the Secure attribute ensures that they will only be transmitted over HTTPS.
The Secure attribute only protects the confidentiality of a cookie against Man in the Middle attackers - there is no integrity protection. E.g. An attacker could still edit a cookie in transit, but not read it
This attribute disables the cookie from being accessed via client-side JavaScript. This is the number one cookie protection against Cross-Site Scripting.
HttpOnly enabled cookies can still be replaced by overflowing the cookie jar from JavaScript
The Domain attribute specifies which hosts can receive a cookie.
- If unspecified, the attribute defaults to the same host that set the cookie, excluding subdomains.
- If Domain is specified, then subdomains are always included.
Therefore, specifying Domain is less restrictive than omitting it. However, it can be helpful when subdomains need to share information about a user.
For example, if you set Domain=mozilla.org
, cookies are available on subdomains like developer.mozilla.org
.
The Path attribute indicates a URL path that must exist in the requested URL in order to send the Cookie header. The %x2F
("/") character is considered a directory separator, and subdirectories match as well.
For example, if you set Path=/docs
, these request paths match:
/docs
/docs/
/docs/Web/
/docs/Web/HTTP
But these request paths don't:
/
/docsets
/fr/docs
The SameSite attribute lets servers specify whether/when cookies are sent with cross-site requests. This provides some protection against Cross-Site Request Forgery attacks (CSRF).
There are three different settings for the SameSite attribute:
- Strict - The cookie is only sent to the site where it originated.
- Lax - Similar to "Strict" except that cookies are sent when the user navigates to the cookie's origin site.
- E.g. Following a link from an external site.
- None - specifies that cookies are sent on both originating and cross-site requests, but only in secure contexts (i.e., if SameSite=None then the Secure attribute must also be set).
If no SameSite attribute is set, the cookie is treated as Lax.
Because of the design of the cookie mechanism, a server can't confirm that a cookie was set from a secure origin or even tell where a cookie was originally set.
A vulnerable application on a subdomain can set a cookie with the Domain attribute, which gives access to that cookie on all other subdomains.
However, you can use Cookie Prefixes to assert specific facts about the cookie. The browser will reject cookies with these prefixes if they don't comply with their restrictions.
Two prefixes are available:
If a cookie name has this prefix, it's accepted in a Set-Cookie
header only if it meets all these conditions:
- It's also marked with the "Secure" attribute
- It was sent from a secure origin
- It does not include a Domain attribute
- It has the Path attribute set to
/
This way, these cookies can be seen as "domain-locked".
If a cookie name has this prefix, it's accepted in a Set-Cookie header only if it meets all these conditions:
- It's marked with the Secure attribute
- It was sent from a secure origin.
This is weaker than the __Host- prefix.
This adds restrictions on cookies marked with the "Secure" attribute. Currently, Secure cookies cannot be accessed by insecure (e.g. HTTP) origins. However, insecure origins can still add Secure cookies, delete them, or indirectly evict them. This feature modifies the cookie jar so that insecure origins cannot in any way touch Secure cookies. This does leave a carve out for cookie eviction, which still may cause the deletion of Secure cookies, but only after all non-Secure cookies are evicted.
Ooie-gooie and fresh out of the oven, perfectly golden brown. Here are some attributes that would go into making one of the most secure cookies out there!
- Cookie Prefix:
__Host
- Path:
/
- Secure:
True
- HttpOnly:
True
- SameSite:
Strict
Ideally the cookie is also cryptographically signed or encrypted, but how that is done is typically up to the implementation.
For instance, in the next section the Plug library gives you the ability to perform those actions within the put_resp_cookie/4
function call. But if you store JSON Web Tokens (JWTs) as the value of your cookie, you can achieve similar signature results through the JWTs themselves.
In the Phoenix Framework, you would use functionality found within the Plug library to set a cookie.
Given the "Perfect Cookie" outlined above, how would you assign that cookie using the Plug library?
Fill out the put_resp_cookie/4
function arguments with the settings outlined in the previous section, no other code changes should be necessary.
cookie_name = "CHANGE_ME_TOO"
conn
|> Plug.Conn.put_resp_cookie(
cookie_name,
<<42::16>>
# domain: ,
# path: ,
# secure: ,
# http_only: ,
# same_site:
)
<- Previous Module: Elixir Security || Next Module: Security Anti-Patterns ->