up
This commit is contained in:
parent
31f83a5e1b
commit
4ffd79e337
65 changed files with 1481 additions and 28000 deletions
|
@ -121,9 +121,34 @@ The example below correctly shows image sizes, because `window.onload` waits for
|
|||
|
||||
## window.onunload
|
||||
|
||||
When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows. But we can't cancel the transition to another page.
|
||||
When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows.
|
||||
|
||||
For that we should use another event -- `onbeforeunload`.
|
||||
This event is a good place to send out analytics.
|
||||
|
||||
E.g. we have a script that gathers some data about mouse clicks, scrolls, viewed page areas -- the statistics that can help us to see what users want.
|
||||
|
||||
Then `onunload` is the best time to send it out. Regular networking methods such as [fetch](info:fetch-basics) or [XMLHttpRequest](info:xmlhttprequest) don't work well, because we're in the process of leaving the page.
|
||||
|
||||
So, there exist `navigator.sendBeacon(url, data)` method for such needs, described in the specification <https://w3c.github.io/beacon/>.
|
||||
|
||||
It sends the data in background. The transition to another page is not delayed: the browser leaves the page and performs `sendBeacon` in background.
|
||||
|
||||
Here's how to use it:
|
||||
```js
|
||||
let analyticsData = { /* object with gathered data */ };
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
|
||||
};
|
||||
```
|
||||
|
||||
- The request is sent as POST.
|
||||
- We can send not only a string, but also forms and other formats, as described in the chapter <info:fetch-basics>, but usually it's a stringified object.
|
||||
- The data is limited by 64kb.
|
||||
|
||||
When the `sendBeacon` request is finished, the browser probably has already left the document, so there's no way to get server response (which is usually empty for analytics).
|
||||
|
||||
If we want to cancel the transition to another page, we can't do it here. But we can use another event -- `onbeforeunload`.
|
||||
|
||||
## window.onbeforeunload [#window.onbeforeunload]
|
||||
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
# Cookies, document.cookie
|
||||
|
||||
Cookies are small strings of data that are stored directly in the browser. They are not a part of Javascript, but rather a part of HTTP protocol, defined by [RFC 6265](https://tools.ietf.org/html/rfc6265) specification.
|
||||
Cookies are small strings of data that are stored directly in the browser. They are a part of HTTP protocol, defined by [RFC 6265](https://tools.ietf.org/html/rfc6265) specification.
|
||||
|
||||
Most of the time, cookies are set by a web server.
|
||||
Most of the time, cookies are set by a web server. Then they are automatically added to every request to the same domain.
|
||||
|
||||
One of the most widespread use of cookies is authentication:
|
||||
One of the most widespread use cases is authentication:
|
||||
|
||||
1. Upon sign in, the server uses `Set-Cookie` HTTP-header in a response to set a cookie with "session identifier".
|
||||
2. The browser stores the cookie.
|
||||
3. Next time when the request is set to the same domain, the browser sends the over the net using `Cookie` HTTP-header.
|
||||
4. So the server knows who made the request.
|
||||
1. Upon sign in, the server uses `Set-Cookie` HTTP-header in the response to set a cookie with "session identifier".
|
||||
2. Next time when the request is set to the same domain, the browser sends the over the net using `Cookie` HTTP-header.
|
||||
3. So the server knows who made the request.
|
||||
|
||||
The browser provides a special accessor `document.cookie` for cookies.
|
||||
We can also access cookies from the browser, using `document.cookie` property.
|
||||
|
||||
There are many tricky things about cookies and their options. In this chapter we'll cover them in detail.
|
||||
|
||||
|
@ -32,11 +31,11 @@ alert( document.cookie ); // cookie1=value1; cookie2=value2;...
|
|||
```
|
||||
|
||||
|
||||
The string consist of `name=value` pairs, delimited by `; `. Each one is a separate cookie.
|
||||
The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie.
|
||||
|
||||
To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that.
|
||||
|
||||
To make things simple, at the end of the chapter you'll find a few functions to manipulate cookies.
|
||||
We leave it as an excercise for the reader. Also, at the end of the chapter you'll find helper functions to manipulate cookies.
|
||||
|
||||
## Writing to document.cookie
|
||||
|
||||
|
@ -51,26 +50,26 @@ document.cookie = "user=John"; // update only cookie named 'user'
|
|||
alert(document.cookie); // show all cookies
|
||||
```
|
||||
|
||||
If you run it, then probably you'll see multiple cookies. That's because `document.cookie=` operation does not overwrite all cookies, but only `user`.
|
||||
If you run it, then probably you'll see multiple cookies. That's because `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`.
|
||||
|
||||
Technically, name and value can have any characters, but to keep the formatting valid they should be escaped using a built-in `encodeURIComponent` function:
|
||||
|
||||
```js run
|
||||
// special values, need encoding
|
||||
let name = "<>";
|
||||
let value = "="
|
||||
let name = "my name";
|
||||
let value = "John Smith"
|
||||
|
||||
// encodes the cookie as %3C%3E=%3D
|
||||
// encodes the cookie as my%20name=John%20Smith
|
||||
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
||||
|
||||
alert(document.cookie); // ...; %3C%3E=%3D
|
||||
alert(document.cookie); // ...; my%20name=John%20Smith
|
||||
```
|
||||
|
||||
|
||||
```warn header="Limitations"
|
||||
There are few limitations:
|
||||
- The `name=value` pair, after `encodeURIComponent`, should not exceed 4kb. So we can't store anything huge in a cookie.
|
||||
- The total number of cookies per domain is limited to 20+, depending on a browser.
|
||||
- The total number of cookies per domain is limited to around 20+, the exact limit depends on a browser.
|
||||
```
|
||||
|
||||
Cookies have several options, many of them are important and should be set.
|
||||
|
@ -87,7 +86,7 @@ document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"
|
|||
|
||||
The url path prefix, where the cookie is accessible. Must be absolute. By default, it's the current path.
|
||||
|
||||
If a cookie is set with `path=/mypath`, it's visible at `/mypath` and `/mypath/*`, but not at `/page` or `/mypathpage`.
|
||||
If a cookie is set with `path=/admin`, it's visible at pages `/admin` and `/admin/something`, but not at `/home` or `/adminpage`.
|
||||
|
||||
Usually, we set `path=/` to make the cookie accessible from all website pages.
|
||||
|
||||
|
@ -95,11 +94,11 @@ Usually, we set `path=/` to make the cookie accessible from all website pages.
|
|||
|
||||
- **`domain=site.com`**
|
||||
|
||||
Domain where the cookie is accessible.
|
||||
A domain where the cookie is accessible. In practice though, there are limitations. We can't set any domain.
|
||||
|
||||
By default, a cookie is accessible only at the domain that set it. So, if the cookie was set by `site.com`, we won't get it `other.com`.
|
||||
|
||||
...But what's more tricky, we also won't get the cookie at a subdomain `forum.site.com`:
|
||||
...But what's more tricky, we also won't get the cookie at a subdomain `forum.site.com`!
|
||||
|
||||
```js
|
||||
// at site.com
|
||||
|
@ -125,6 +124,8 @@ alert(document.cookie); // with user
|
|||
|
||||
For historical reasons, `domain=.site.com` (a dot at the start) also works this way, it might better to add the dot to support very old browsers.
|
||||
|
||||
So, `domain` option allows to make a cookie accessible at subdomains.
|
||||
|
||||
## expires, max-age
|
||||
|
||||
By default, if a cookie doesn't have one of these options, it disappears when the browser is closed. Such cookies are called "session cookies"
|
||||
|
@ -144,13 +145,13 @@ date = date.toUTCString();
|
|||
document.cookie = "user=John; expires=" + date;
|
||||
```
|
||||
|
||||
If we set `expires` to the past, the cookie will be deleted.
|
||||
If we set `expires` to a date in the past, the cookie is deleted.
|
||||
|
||||
- **`max-age=3600`**
|
||||
|
||||
An alternative to `expires`, specifies the cookie expiration in seconds.
|
||||
An alternative to `expires`, specifies the cookie expiration in seconds from the current moment.
|
||||
|
||||
Can be either a number of seconds from the current moment, or zero/negative for immediate expiration (to remove the cookie):
|
||||
If zero or negative, then the cookie is deleted:
|
||||
|
||||
```js
|
||||
// cookie will die +1 hour from now
|
||||
|
@ -168,11 +169,12 @@ The cookie should be transferred only over HTTPS.
|
|||
|
||||
**By default if we set a cookie at `http://site.com`, then it also appears at `https://site.com` and vise versa.**
|
||||
|
||||
That is, cookies only check the domain, they do not distinguish between the protocols.
|
||||
That is, cookies are domain-based, they do not distinguish between the protocols.
|
||||
|
||||
With this option, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, then the flag can prevent this.
|
||||
With this option, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, then the flag is the right thing.
|
||||
|
||||
```js
|
||||
// assuming we're on https:// now
|
||||
// set the cookie secure (only accessible if over HTTPS)
|
||||
document.cookie = "user=John; secure";
|
||||
```
|
||||
|
@ -185,9 +187,9 @@ To understand when it's useful, let's introduce the following attack scenario.
|
|||
|
||||
### XSRF attack
|
||||
|
||||
Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` on every request, so that it recognizes you and performs all sensitive financial operations.
|
||||
Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request, so that it recognizes you and performs all sensitive financial operations.
|
||||
|
||||
Now, while browsing the web in another window, you occasionally come to another site `evil.com`, that has a `<form action="https://bank.com/pay">` with hacker's account and JavaScript code that submits it automatically.
|
||||
Now, while browsing the web in another window, you occasionally come to another site `evil.com`, that automatically submits a form `<form action="https://bank.com/pay">` to `bank.com` with input fields that initiate a transaction to the hacker's account.
|
||||
|
||||
The form is submitted from `evil.com` directly to the bank site, and your cookie is also sent, just because it's sent every time you visit `bank.com`. So the bank recognizes you and actually performs the payment.
|
||||
|
||||
|
@ -197,31 +199,33 @@ That's called a cross-site request forgery (or XSRF) attack.
|
|||
|
||||
Real banks are protected from it of course. All forms generated by `bank.com` have a special field, so called "xsrf protection token", that an evil page can't neither generate, nor somehow extract from a remote page (it can submit a form there, but can't get the data back).
|
||||
|
||||
But that takes time to implement: we need to ensure that every form has the token field, and we must also check all requests.
|
||||
|
||||
### Enter cookie samesite option
|
||||
|
||||
Now, cookie `samesite` option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens".
|
||||
The cookie `samesite` option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens".
|
||||
|
||||
It has two possible values:
|
||||
|
||||
- **`samesite=strict`, same as `samesite` without value**
|
||||
- **`samesite=strict` (same as `samesite` without value)**
|
||||
|
||||
A cookie with `samesite=strict` is never sent if the user comes from outside the site.
|
||||
|
||||
In other words, whether a user follows a link from the mail or submits a form from `evil.com`, or does any operation with the site that originates from another domain, the cookie is not sent. Then the XSRF attack will fail, as `bank.com` will not recognize the user without the cookie, and will not proceed with the payment.
|
||||
In other words, whether a user follows a link from their mail or submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent.
|
||||
|
||||
The protection is quite reliable. Only operations that come from `bank.com` will send the samesite cookie.
|
||||
If authentication cookies have `samesite` option, then XSRF attack has no chances to succeed, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment.
|
||||
|
||||
The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite` cookie.
|
||||
|
||||
Although, there's a small inconvenience.
|
||||
|
||||
When a user follows a legitimate link to `bank.com`, like from their own notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case.
|
||||
|
||||
We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`.
|
||||
|
||||
Then a person coming from outside of the site will see a welcome, but payments must be initiated from the bank website.
|
||||
We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then a person coming from outside of the site will see a welcome, but payments must be initiated from the bank website.
|
||||
|
||||
- **`samesite=lax`**
|
||||
|
||||
Another approach to keep user experience is to use `samesite=lax`, a more relaxed value.
|
||||
A more relaxed approach that also protects from XSRF and doesn't break user experience.
|
||||
|
||||
Lax mode, just like `strict`, forbids the browser to send cookies when coming from outside the site, but adds an exception.
|
||||
|
||||
|
@ -234,22 +238,24 @@ A `samesite=lax` cookie is sent if both of these conditions are true:
|
|||
|
||||
That's usually true, but if the navigation is performed in an `<iframe>`, then it's not top-level. Also, AJAX requests do not perform any navigation, hence they don't fit.
|
||||
|
||||
So, what `samesite=lax` does is basically allows a most common "open URL" operation to bring cookies. Something more complicated, like AJAX request from another site or a form submittion loses cookies.
|
||||
So, what `samesite=lax` does is basically allows a most common "go to URL" operation to have cookies. E.g. opening a website link from notes satisfies these conditions.
|
||||
|
||||
But anything more complicated, like AJAX request from another site or a form submittion loses cookies.
|
||||
|
||||
If that's fine for you, then adding `samesite=lax` will probably not break the user experience and add protection.
|
||||
|
||||
Overall, `samesite` is great, but it has an important drawback:
|
||||
- `samesite` is not supported, ignored by old browsers (like year 2017).
|
||||
Overall, `samesite` is a great option, but it has an important drawback:
|
||||
- `samesite` is ignored (not supported) by old browsers, year 2017 or so.
|
||||
|
||||
**So if we solely rely on `samesite` to provide protection, then old browsers will be totally vulnerable.**
|
||||
**So if we solely rely on `samesite` to provide protection, then old browsers will be vulnerable.**
|
||||
|
||||
But we surely can use `samesite` together with other protection measures, like xsrf tokens, to add an additional layer of defence.
|
||||
But we surely can use `samesite` together with other protection measures, like xsrf tokens, to add an additional layer of defence and then, in the future, when old browsers die out, we'll probably be able to drop xsrf tokens.
|
||||
|
||||
## httpOnly
|
||||
|
||||
This option has nothing to do with Javascript, but we have to mention it for completeness.
|
||||
|
||||
Only the server, when it uses `Set-Cookie` to set a cookie, may set the `httpOnly` option.
|
||||
The web-server uses `Set-Cookie` header to set a cookie. And it may set the `httpOnly` option.
|
||||
|
||||
This option forbids any JavaScript access to the cookie. We can't see such cookie or manipulate it using `document.cookie`.
|
||||
|
||||
|
@ -264,7 +270,7 @@ But if a cookie is `httpOnly`, then `document.cookie` doesn't see it, so it is p
|
|||
|
||||
Here's a small set of functions to work with cookies, more conveinent than a manual modification of `document.cookie`.
|
||||
|
||||
There exist many cookie libraries for that, so these are for demo purposes. Fully working though:
|
||||
There exist many cookie libraries for that, so these are for demo purposes. Fully working though.
|
||||
|
||||
|
||||
### getCookie(name)
|
||||
|
@ -284,11 +290,9 @@ function getCookie(name) {
|
|||
}
|
||||
```
|
||||
|
||||
Here the regexp is generated dynamically, to match `; name=<value>`.
|
||||
Here `new RegExp` is generated dynamically, to match `; name=<value>`.
|
||||
|
||||
Please note that a cookie value can be an arbitrary string. If it contains characters that break the formatting, for instance spaces or `;`, such characters are encoded.
|
||||
|
||||
To decode them, we need to use a built-in `decodeURIComponent` function, the function does it also.
|
||||
Please note that a cookie value is encoded, so `getCookie` uses a built-in `decodeURIComponent` function to decode it.
|
||||
|
||||
### setCookie(name, value, options)
|
||||
|
||||
|
@ -413,9 +417,9 @@ GDPR is not only about cookies, it's about other privacy-related issues too, but
|
|||
Cookie options:
|
||||
- `path=/`, by default current path, makes the cookie visible only under that path.
|
||||
- `domain=site.com`, by default a cookie is visible on current domain only, if set explicitly to the domain, makes the cookie visible on subdomains.
|
||||
- `expires/max-age` set cookie expiration time, without them the cookie dies when the browser is closed.
|
||||
- `expires` or `max-age` sets cookie expiration time, without them the cookie dies when the browser is closed.
|
||||
- `secure` makes the cookie HTTPS-only.
|
||||
- `samesite` forbids browser to send the cookie with requests coming from outside the site, helps to prevent XSRF attacks.
|
||||
- `samesite` forbids the browser to send the cookie with requests coming from outside the site, helps to prevent XSRF attacks.
|
||||
|
||||
Additionally:
|
||||
- Third-party cookies may be forbidden by the browser, e.g. Safari does that by default.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue