/docs/security

HTMLの共通設定とセキュリティ

認証やCSPはHonoアプリ側で管理し、言語、nonce、外部iframeの設定を各画面に反映します。

標準で作成されるルートを確認する

標準では、一覧、ビューアー、スライド本体、発表画面、発表者画面、印刷画面が作成されます。これらのルートにhono-decks独自の認証は付きません。外部iframe用の/:slug/embedだけは、embedを指定するまで作成されません。不要な画面はrouter.pagesで無効にしてください。

認証、認可、CSPヘッダーはHonoアプリが担当します。hono-decksは、アプリから渡された言語やnonceを生成するHTMLへ反映します。

Honoで言語を判定し、すべての画面へ渡す

HonoのlanguageDetector()は、既定でクエリ、Cookie、Accept-Languageの順に言語を判定し、c.get("language")から取得できるようにします。

TypeScript
import { languageDetector } from "hono/language"

app.use(languageDetector({
  supportedLanguages: ["ja", "en"],
  fallbackLanguage: "en",
}))

document.langにその値を渡すと、一覧、ビューアー、スライド、印刷、発表画面、発表者画面の<html lang>が揃います。

同じnonceをCSPヘッダーとHTMLへ渡す

nonceはリクエストごとに作り、HonoのVariablesへ保存します。CSPヘッダーに含めた値と、document.nonceが返す値は必ず同じにします。

アプリ側のミドルウェア
import { Hono } from "hono"

type AppEnv = {
  Variables: {
    cspNonce: string
    language: string
  }
}

const app = new Hono<AppEnv>()

app.use("*", async (c, next) => {
  const nonce = crypto.randomUUID()
  c.set("cspNonce", nonce)
  c.header("Content-Security-Policy", [
    "default-src 'self'",
    "object-src 'none'",
    "base-uri 'self'",
    `script-src 'self' 'nonce-${nonce}'`,
    `style-src 'self' 'nonce-${nonce}'`,
    "img-src 'self' data: https:",
    "frame-src 'self' https://www.youtube.com",
  ].join("; "))
  await next()
})
hono-decksの設定
decks.router({
  document: {
    lang: ({ c }) => c.get("language"),
    nonce: ({ c }) => c.get("cspNonce"),
    head: ({ surface }) => (
      <meta name="hono-decks-surface" content={surface} />
    ),
  },
})

hono-decksは指定されたnonceを、パッケージが生成する<style><script>へ付けます。YouTube、外部画像、Islandなどを使う場合は、アプリ側のCSPにも必要な配信元を追加してください。

埋め込みを許可するオリジンを指定する

外部サイトにiframeで表示するときだけembedを有効にし、親ページのオリジンを列挙します。

TypeScript
decks.router({
  embed: {
    frameAncestors: ["https://blog.example.com"],
    document: { nonce: ({ c }) => c.get("cspNonce") },
    viewer: { controls: false },
  },
})

公開前に実際のレスポンスを確認する

下書きや発表者画面
本番で不要なルートが404になることを確認します。
CSP
ブラウザの開発者ツールでCSP違反がなく、nonceがヘッダーとHTMLで一致することを確認します。
外部埋め込み
許可した親サイトでは表示でき、許可していないオリジンでは拒否されることを確認します。

HTMLと埋め込みのAPIを見る →