Skip to content

Commit bba6f2f

Browse files
committed
Improve security docs
1 parent 2c5de7f commit bba6f2f

File tree

1 file changed

+189
-21
lines changed

1 file changed

+189
-21
lines changed

src/routes/solid-start/guides/security.mdx

Lines changed: 189 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,203 @@
22
title: Security
33
---
44

5-
As a non-opinionated framework, SolidStart doesn't enforce any security practices, though it enables enables developers to implement them as needed.
6-
It is important to know what are the requirements for your own app and implement the fitting security measures.
7-
If at any point you are unsure about the security of your app, or how to achieve something within the constraints of SolidStart reach us on [Discord](https://discord.gg/solidjs).
5+
This guide walks you through how to implement common security mechanisms in SolidStart.
86

9-
Below you will find a few notes on how to establish some measures.
7+
## XSS (Cross Site Scripting)
108

11-
## Security Headers
9+
Solid automatically escape values passed to JSX expressions to reduce the risk of XSS attacks.
10+
However, this protection does not apply when using [`innerHTML`](https://docs.solidjs.com/reference/jsx-attributes/innerhtml-or-textcontent#innerhtml-or-textcontent).
1211

13-
Through the use of a [middleware](/solid-start/reference/server/create-middleware#example) it is possible to tab into the `onRequest` event handlers and make sure every request going through your servers have the proper security headers set.
14-
With this, it is possible to setup headers like `Content-Security-Policy`, `X-Frame-Options`, `X-XSS-Protection`, `X-Content-Type-Options`, among others.
12+
To protect your application from XSS attacks:
1513

16-
### Nonces
14+
1. Avoid using [`innerHTML`](/reference/jsx-attributes/innerhtml-or-textcontent#innerhtml-or-textcontent) when possible.
15+
If you must use it, sanitize the content before rendering with libraries such as [DOMPurify](https://github.com/cure53/DOMPurify).
16+
2. Set a Content Security Policy (CSP).
17+
3. Validate and sanitize user inputs.
18+
Always validate form inputs on the server in addition to the client.
19+
4. Use the `HttpOnly` and `Secure` attributes on cookies.
1720

18-
When using `Content-Security-Policy` it is possible to use nonces to allow inline scripts and styles to be executed.
19-
SolidStart enables that smoothly in the [`entry-server.tsx`](/solid-start/reference/entrypoints/entry-server).
21+
## CSP (Content Security Policy)
2022

21-
By passing generating the `nonce` within a middleware and storing it in the `request.locals` object, it is possible to use it in the `entry-server.tsx` to generate the `Content-Security-Policy` header.
23+
To configure the `Content-Security-Policy` HTTP header, you can use a [middleware](/solid-start/advanced/middleware).
2224

23-
## Cross Request Forgery (CSRF)
25+
### With nonce (recommended)
2426

25-
There are multiple ways to add CSRF Protection to your SolidStart app.
26-
The quickest and most common way is to check the `request.referrer` header when the HTTP method is `POST`, `PUT`, `PATCH` or `DELETE`.
27-
This can also be achieved through an `onRequest` [middleware](/solid-start/reference/server/create-middleware#example).
27+
If you want to use a strict CSP with nonces:
2828

29-
## Cross Site Scripting (XSS)
29+
1. Create a middleware that configures the CSP header, then register it to run on the [`onRequest`](/solid-start/advanced/middleware#onrequest) event.
30+
2. Store the nonce in the [`locals`](/solid-start/advanced/middleware#locals) object.
31+
3. Configure SolidStart to use the nonce in your [`entry-server.tsx`](/solid-start/reference/entrypoints/entry-server) file.
3032

31-
SolidStart automatically escape inserts and attributes in HTML.
32-
The exception is when HTML is inserted via the `innerHTML` property, which bypasses the escaping.
33-
Additionally, it's important to note that `<noscript>` are also outside of the purview of SolidStart, since those tags and its contents are evaluated even without JavaScript.
34-
It is important to sanitize any strings in attributes, especially when inside `<noscript>` tags.
33+
<TabsCodeBlocks>
34+
<div id="Middleware">
3535

36-
As a rule-of-thumb it is recommended to avoid injecting HTML into your page as much as possible, make sure the contents of `<noscript>` are properly sanitized, and add a strict Content Security Policy to your application.
36+
```tsx
37+
import { createMiddleware } from "@solidjs/start/middleware";
38+
import { randomBytes } from "crypto";
39+
40+
export default createMiddleware({
41+
onRequest: (event) => {
42+
const nonce = randomBytes(16).toString("base64");
43+
44+
event.locals.nonce = nonce;
45+
46+
const csp = `
47+
default-src 'self';
48+
script-src 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval';
49+
object-src 'none';
50+
base-uri 'none';
51+
frame-ancestors 'none';
52+
form-action 'self';
53+
`.replace(/\s+/g, " ");
54+
55+
event.response.headers.set("Content-Security-Policy", csp);
56+
},
57+
});
58+
```
59+
60+
</div>
61+
<div id="entry-server.tsx">
62+
63+
```tsx {6} title="src/entry-server.tsx"
64+
// @refresh reload
65+
import { createHandler, StartServer } from "@solidjs/start/server";
66+
67+
export default createHandler(
68+
() => <StartServer /* ... */ />,
69+
(event) => ({ nonce: event.locals.nonce })
70+
);
71+
```
72+
73+
</div>
74+
</TabsCodeBlocks>
75+
76+
### Without nonce
77+
78+
To configure CSP without a nonce, create a middleware that configures the CSP header, then register it to run on the [`onBeforeResponse`](/solid-start/advanced/middleware#onbeforeresponse) event:
79+
80+
```tsx
81+
import { createMiddleware } from "@solidjs/start/middleware";
82+
83+
export default createMiddleware({
84+
onBeforeResponse: (event) => {
85+
const csp = `
86+
default-src 'self';
87+
font-src 'self' ;
88+
object-src 'none';
89+
base-uri 'none';
90+
frame-ancestors 'none';
91+
form-action 'self';
92+
`.replace(/\s+/g, " ");
93+
94+
event.response.headers.set("Content-Security-Policy", csp);
95+
},
96+
});
97+
```
98+
99+
## CORS (Cross-Origin Resource Sharing)
100+
101+
When you want to allow other applications to access your API endpoints, you can use a middleware to configure the CORS headers:
102+
103+
```tsx
104+
import { createMiddleware } from "@solidjs/start/middleware";
105+
import { json } from "@solidjs/router";
106+
107+
const TRUSTED_ORIGINS = ["https://my-app.com", "https://another-app.com"];
108+
109+
export default createMiddleware({
110+
onBeforeResponse: (event) => {
111+
const { request, response } = event;
112+
113+
response.headers.append("Vary", "Origin, Access-Control-Request-Method");
114+
115+
const origin = request.headers.get("Origin");
116+
const requestUrl = new URL(request.url);
117+
const isApiRequest = requestUrl && requestUrl.pathname.startsWith("/api");
118+
119+
if (isApiRequest && origin && TRUSTED_ORIGINS.includes(origin)) {
120+
// Handle preflight requests.
121+
if (
122+
request.method === "OPTIONS" &&
123+
request.headers.get("Access-Control-Request-Method")
124+
) {
125+
// Preflight requests are standalone, so we immediately send a response.
126+
return json(null, {
127+
headers: {
128+
"Access-Control-Allow-Origin": origin,
129+
"Access-Control-Allow-Methods": "OPTIONS, POST, PUT, PATCH, DELETE",
130+
"Access-Control-Allow-Headers": "Authorization, Content-Type",
131+
},
132+
});
133+
}
134+
135+
// Handle normal requests.
136+
response.headers.set("Access-Control-Allow-Origin", origin);
137+
}
138+
},
139+
});
140+
```
141+
142+
## CSRF (Cross-Site Request Forgery)
143+
144+
To prevent CSRF attacks, you can use a middleware to block untrusted requests.
145+
146+
```tsx
147+
import { createMiddleware } from "@solidjs/start/middleware";
148+
import { json } from "@solidjs/router";
149+
150+
const SAFE_METHODS = ["GET", "HEAD", "OPTIONS", "TRACE"];
151+
const TRUSTED_ORIGINS = ["https://another-app.com"];
152+
153+
export default createMiddleware({
154+
onRequest: (event) => {
155+
const { request } = event;
156+
157+
if (!SAFE_METHODS.includes(request.method)) {
158+
const requestUrl = new URL(request.url);
159+
const origin = request.headers.get("Origin");
160+
161+
// If we have an Origin header, check it against our allowlist.
162+
if (origin) {
163+
const parsedOrigin = new URL(origin);
164+
165+
if (
166+
parsedOrigin.origin !== requestUrl.origin &&
167+
!TRUSTED_ORIGINS.includes(parsedOrigin.host)
168+
) {
169+
return json({ error: "origin invalid" }, { status: 403 });
170+
}
171+
}
172+
173+
// If we are serving via TLS and have no Origin header, prevent against
174+
// CSRF via HTTP man-in-the-middle attacks by enforcing strict Referer
175+
// origin checks.
176+
if (!origin && requestUrl.protocol === "https:") {
177+
const referer = request.headers.get("Referer");
178+
179+
if (!referer) {
180+
return json({ error: "referer not supplied" }, { status: 403 });
181+
}
182+
183+
const parsedReferer = new URL(referer);
184+
185+
if (parsedReferer.protocol !== "https:") {
186+
return json({ error: "referer invalid" }, { status: 403 });
187+
}
188+
189+
if (
190+
parsedReferer.host !== requestUrl.host &&
191+
!TRUSTED_ORIGINS.includes(parsedReferer.host)
192+
) {
193+
return json({ error: "referer invalid" }, { status: 403 });
194+
}
195+
}
196+
}
197+
},
198+
});
199+
```
200+
201+
This example demonstrates a basic implementation of CSRF protection that checks for the `Origin` and `Referer` headers and blocks requests that are not from trusted origins.
202+
In addition, consider implementing a more comprehensive CSRF protection mechanism such as [Double-Submit Cookie Pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#alternative-using-a-double-submit-cookie-pattern).
203+
204+
We highly recommend you read through [Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) for more guidance.

0 commit comments

Comments
 (0)