Integrasi Content Security Policy (CSP) berbasis nonce pada arsitektur hybrid—Go Fiber sebagai reverse-proxy/SSR handler dan React/Next.js di sisi rendering engine—sering memicu hydration mismatch. Pesan peringatan browser seperti Warning: Extra attributes from the server: nonce atau perbedaan nilai atribut antara Virtual DOM dan Real DOM terjadi karena siklus hidup atribut nonce diatur secara khusus oleh spesifikasi browser dan framework SSR.
Akar Masalah: Anatomi Hydration Mismatch CSP Nonce
Masalah ini bersumber dari dua faktor utama: sifat kriptografis nonce dan perilaku keamanan DOM di browser modern.
- Sifat Dinamis Per-Request: Backend Go Fiber menghasilkan string acak kriptografis unik setiap kali request HTTP masuk. Nilai ini disuntikkan ke header HTTP
Content-Security-Policydan atributnoncepada tag<script>HTML mentah. - Penyembunyian Nonce oleh Browser (CSP Spec): Sesuai spesifikasi CSP Level 3, browser menyembunyikan nilai atribut
noncedari pembacaan DOM standar setelah script dieksekusi guna mencegah eksfiltrasi melalui serangan XSS (misalnya via CSS selector atauelement.getAttribute('nonce')). Properti IDLscript.noncetetap menyimpan nilai, tetapi atribut konten HTML di DOM tree diubah menjadi string kosong atau dihapus. - Fase Rekonsiliasi React: Saat engine React di sisi client melakukan rehidrasi, Virtual DOM client mungkin diinisialisasi tanpa nilai nonce yang sama atau mengharapkan struktur DOM yang identik. Perbedaan antara HTML renderan server (memiliki atribut
nonce="...") dan representasi client memicu error hydration mismatch.
Implementasi Middleware CSP Nonce di Go Fiber
Gunakan package standar crypto/rand untuk menghasilkan nonce berbasis Base64 berkecepatan tinggi, simpan ke c.Locals, dan inject ke header response via middleware kustom.
package main
import (
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/gofiber/fiber/v2"
)
func CSPNonceMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
// Generate 16 bytes random buffer
nonceBytes := make([]byte, 16)
if _, err := rand.Read(nonceBytes); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to generate CSP nonce")
}
nonce := base64.StdEncoding.EncodeToString(nonceBytes)
// Simpan di context untuk diakses engine template atau SSR bridge
c.Locals("cspNonce", nonce)
// Set header Content-Security-Policy
cspHeader := fmt.Sprintf("default-src 'self'; script-src 'self' 'nonce-%s'; style-src 'self' 'unsafe-inline';", nonce)
c.Set("Content-Security-Policy", cspHeader)
return c.Next()
}
}
Setelah middleware terpasang, handler yang bertugas merender halaman SSR harus mengekstraksi nonce ini dan mengirimkannya ke payload HTML shell atau context SSR.
func SSRHandler(c *fiber.Ctx) error {
nonce, ok := c.Locals("cspNonce").(string)
if !ok {
nonce = ""
}
// Simulasi passing nonce ke root template frontend
return c.Render("index", fiber.Map{
"Nonce": nonce,
})
}
Sinkronisasi di Frontend SSR (React)
Untuk mencegah React memicu error saat membaca tag <script> yang disuntikkan server, gunakan kombinasi passing nonce via props/context dan atribut suppressHydrationWarning pada tag bersangkutan.
1. Root Document Template
Jika menggunakan custom SSR template (seperti renderToString dengan HTML template wrapper di Go):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSR App</title>
<!-- Script hidrasi aplikasi frontend -->
<script nonce="{{.Nonce}}" src="/static/bundle.js" defer></script>
</head>
<body>
<div id="root">{{.AppHTML}}</div>
<script nonce="{{.Nonce}}" id="__INITIAL_DATA__" type="application/json">
{"cspNonce": "{{.Nonce}}"}
</script>
</body>
</html>
2. Penanganan pada Komponen Script Dinamis
Jika komponen React di dalam tree aplikasi perlu menyuntikkan inline script atau third-party tracking, manipulasi atribut harus memperhitungkan hydration phase.
import React from 'react';
interface ScriptProps {
nonce?: string;
content: string;
}
export const SafeInlineScript: React.FC<ScriptProps> = ({ nonce, content }) => {
return (
<script
nonce={nonce}
suppressHydrationWarning={true}
dangerouslySetInnerHTML={{ __html: content }}
/>
);
};
Catatan: Penggunaan
suppressHydrationWarning={true}pada elemen<script>menginstruksikan reconciler React untuk mengabaikan perbedaan atribut antara SSR snapshot dan real DOM, mencegah console warning tanpa merusak eksekusi script.
Strategi Next.js Custom Document
Jika Go Fiber bertindak sebagai reverse proxy di depan server Next.js (Node.js engine), teruskan nonce melalui custom request header:
// Go Fiber reverse proxy middleware
c.Request().Header.Set("x-nonce", nonce)
Pada Next.js (Pages Router), konsumsi header tersebut di _document.tsx:
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document';
interface CustomDocumentProps {
nonce?: string;
}
class MyDocument extends Document<CustomDocumentProps> {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
const nonce = ctx.req?.headers['x-nonce'] as string | undefined;
return { ...initialProps, nonce };
}
render() {
const { nonce } = this.props;
return (
<Html>
<Head nonce={nonce} />
<body>
<Main />
<NextScript nonce={nonce} />
</body>
</Html>
);
}
}
export default MyDocument;
Debugging dan Verifikasi
Lakukan validasi rantai integrasi untuk memastikan CSP aktif tanpa warning di console:
- Inspeksi Header Response: Jalankan
curl -I http://localhost:3000/pathdan pastikan directivescript-src 'self' 'nonce-...'tersedia dengan nilai acak yang berganti pada setiap request. - Cek Eksekusi Script: Buka tab Console pada DevTools browser. Pastikan tidak ada blokade CSP bertuliskan
Refused to execute inline script because it violates the following Content Security Policy directive.... - Audit Hydration Warning: Pastikan console bersih dari
Warning: Prop `nonce` did not match. Jika masih muncul, pastikan nilai nonce tidak di-hardcode di bundle client.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!