サーバーレンダリングアプリケーションの保護
AdonisJSを使用してサーバーレンダリングアプリケーションを作成している場合は、CSRF、XSS、コンテンツスニッフィングなどの一般的なWeb攻撃からアプリケーションを保護するために、@adonisjs/shield
パッケージを使用する必要があります。
このパッケージはウェブスターターキットとして事前に設定されています。ただし、以下の手順にしたがってパッケージを手動でインストールおよび設定することもできます。
@adonisjs/shield
パッケージは@adonisjs/session
パッケージに依存しているため、セッションパッケージを設定することを忘れないでください。
node ace add @adonisjs/shield
-
検出されたパッケージマネージャーを使用して
@adonisjs/shield
パッケージをインストールします。 -
adonisrc.ts
ファイル内に以下のサービスプロバイダーを登録します。{providers: [// ...other providers() => import('@adonisjs/shield/shield_provider'),]} -
config/shield.ts
ファイルを作成します。 -
start/kernel.ts
ファイル内に以下のミドルウェアを登録します。router.use([() => import('@adonisjs/shield/shield_middleware')])
CSRF保護
CSRF(クロスサイトリクエストフォージェリ)は、悪意のあるウェブサイトがユーザーに明示的な同意なしにフォームの送信を行わせる攻撃です。
CSRF攻撃に対抗するためには、ウェブサイトだけが生成および検証できるCSRFトークン値を持つ非表示の入力フィールドを定義する必要があります。したがって、悪意のあるウェブサイトによってトリガーされるフォームの送信は失敗します。
フォームの保護
@adonisjs/shield
パッケージを設定すると、CSRFトークンのないすべてのフォームの送信は自動的に失敗します。したがって、CSRFトークンを持つ非表示の入力フィールドを定義するためにcsrfField
エッジヘルパーを使用する必要があります。
<form method="POST" action="/">
{{ csrfField() }}
<input type="name" name="name" placeholder="名前を入力してください">
<button type="submit"> 送信 </button>
</form>
<form method="POST" action="/">
<input type="hidden" name="_csrf" value="Q9ghWSf0-3FD9eCiu5YxvKaxLEZ6F_K4DL8o"/>
<input type="name" name="name" placeholder="名前を入力してください"/>
<button type="submit">送信</button>
</form>
フォームの送信時、shield_middleware
は自動的に_csrf
トークンを検証し、有効なCSRFトークンを持つフォームの送信のみを許可します。
例外の処理
CSRFトークンが存在しないか無効な場合、ShieldはE_BAD_CSRF_TOKEN
例外を発生させます。デフォルトでは、AdonisJSは例外をキャプチャし、エラーフラッシュメッセージを含んだフォームにユーザーをリダイレクトします。
Edgeテンプレート内でフラッシュメッセージにアクセスするには、次のようにします。
@error('E_BAD_CSRF_TOKEN')
<p> {{ $message }} </p>
@end
<form method="POST" action="/">
{{ csrfField() }}
<input type="name" name="name" placeholder="名前を入力してください">
<button type="submit"> 送信 </button>
</form>
また、グローバル例外ハンドラ内でE_BAD_CSRF_TOKEN
例外を自己処理することもできます。
import app from '@adonisjs/core/services/app'
import { errors } from '@adonisjs/shield'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
export default class HttpExceptionHandler extends ExceptionHandler {
async handle(error: unknown, ctx: HttpContext) {
if (error instanceof errors.E_BAD_CSRF_TOKEN) {
return ctx.response
.status(error.status)
.send('ページの有効期限が切れました')
}
return super.handle(error, ctx)
}
}
設定リファレンス
CSRFガードの設定はconfig/shield.ts
ファイルに保存されます。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
csrf: {
enabled: true,
exceptRoutes: [],
enableXsrfCookie: true,
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
},
})
export default shieldConfig
-
enabled
-
CSRFガードをオンまたはオフにします。
-
exceptRoutes
-
CSRF保護から除外するルートパターンの配列です。アプリケーションにAPI経由でフォームの送信を受け入れるルートがある場合は、除外する必要があります。
より高度な使用例では、特定のルートを動的に除外するために関数を登録することもできます。
{exceptRoutes: (ctx) => {// /api/で始まるすべてのルートを除外return ctx.request.url().includes('/api/')}} -
enableXsrfCookie
-
有効にすると、Shieldは
XSRF-TOKEN
という名前の暗号化されたクッキーにCSRFトークンを保存します。これにより、Axiosなどのフロントエンドのリクエストライブラリが自動的にXSRF-TOKEN
を読み取り、サーバーレンダリングされたフォームなしでAjaxリクエストを行う際にヘッダーとして設定できます。これにより、Axiosなどのフロントエンドリクエストライブラリが
XSRF-TOKEN
を自動的に読み取り、サーバーレンダリングされたフォームなしでAjaxリクエストを行う際にX-XSRF-TOKEN
ヘッダーとして設定できます。Ajaxリクエストをプログラムでトリガーしない場合は、
enableXsrfCookie
を無効にしておく必要があります。 -
methods
-
保護するHTTPメソッドの配列です。指定されたメソッドのすべての受信リクエストは有効なCSRFトークンを提供する必要があります。
-
cookieOptions
-
XSRF-TOKEN
クッキーの設定です。使用可能なオプションについては、クッキーの設定を参照してください。
CSPポリシーの定義
CSP(コンテンツセキュリティポリシー)は、JavaScript、CSS、フォント、画像などの信頼できるソースを定義することによって、XSS攻撃からアプリケーションを保護します。
CSPガードはデフォルトで無効になっています。ただし、有効にし、ポリシーディレクティブをconfig/shield.ts
ファイル内で設定することをオススメします。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
csp: {
enabled: true,
directives: {
// ポリシーディレクティブをここに記述
},
reportOnly: false,
},
})
export default shieldConfig
-
enabled
-
CSPガードをオンまたはオフにします。
-
directives
-
CSPディレクティブを設定します。使用可能なディレクティブのリストはhttps://content-security-policy.com/で確認できます。
const shieldConfig = defineConfig({csp: {enabled: true,directives: {defaultSrc: [`'self'`],scriptSrc: [`'self'`, 'https://cdnjs.cloudflare.com'],fontSrc: [`'self'`, 'https://fonts.googleapis.com']},reportOnly: false,},})export default shieldConfig -
reportOnly
-
reportOnly
フラグが有効になっている場合、CSPポリシーはリソースをブロックしません。代わりに、違反をreportUri
ディレクティブで設定されたエンドポイントに報告します。const shieldConfig = defineConfig({csp: {enabled: true,directives: {defaultSrc: [`'self'`],reportUri: ['/csp-report']},reportOnly: true,},})また、違反レポートを収集するために
csp-report
エンドポイントを登録します。router.post('/csp-report', async ({ request }) => {const report = request.input('csp-report')})
Nonceの使用
インラインのscript
タグとstyle
タグを許可するには、それらにnonce属性を定義できます。nonce属性の値は、cspNonce
プロパティを使用してEdgeテンプレート内でアクセスできます。
<script nonce="{{ cspNonce }}">
// インラインJavaScript
</script>
<style nonce="{{ cspNonce }}">
/* インラインCSS */
</style>
また、ディレクティブの設定内で@nonce
キーワードを使用してnonceベースのインラインスクリプトとスタイルを許可します。
const shieldConfig = defineConfig({
csp: {
directives: {
defaultSrc: [`'self'`, '@nonce'],
},
},
})
Vite Devサーバーからアセットを読み込む
Viteの統合を使用している場合、Vite Devサーバーが提供するアセットを許可するために次のCSPキーワードを使用できます。
@viteDevUrl
はVite DevサーバーのURLを許可リストに追加します。@viteHmrUrl
はVite HMRウェブソケットサーバーのURLを許可リストに追加します。
const shieldConfig = defineConfig({
csp: {
directives: {
defaultSrc: [`'self'`, '@viteDevUrl'],
connectSrc: ['@viteHmrUrl']
},
},
})
Viteのバンドル出力をCDNサーバーにデプロイしている場合は、@viteDevUrl
を@viteUrl
キーワードに置き換えて、開発サーバーとCDNサーバーの両方からのアセットを許可する必要があります。
directives: {
defaultSrc: [`'self'`, '@viteDevUrl'],
defaultSrc: [`'self'`, '@viteUrl'],
connectSrc: ['@viteHmrUrl']
},
Viteによって注入されるスタイルにNonceを追加する
現在、ViteはDOM内に注入されるstyle
タグにnonce
属性を定義することを許可していません。これに関してはオープンなPRがあり、近々解決されることを期待しています。
HSTSの設定
Strict-Transport-Security(HSTS)レスポンスヘッダーは、ブラウザに常にHTTPSを使用してウェブサイトを読み込むように指示します。
config/shield.ts
ファイルを使用してヘッダーディレクティブを設定できます。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
hsts: {
enabled: true,
maxAge: '180 days',
includeSubDomains: true,
},
})
-
enabled
-
HSTSガードをオンまたはオフにします。
-
maxAge
-
max-age
属性を定義します。値は秒単位の数値または文字列形式の時間表現である必要があります。{// 10秒間記憶するmaxAge: 10,}{// 2日間記憶するmaxAge: '2 days',} -
includeSubDomains
-
includeSubDomains
ディレクティブを定義して、サブドメインに設定を適用します。
X-Frame保護の設定
X-Frame-Optionsヘッダーは、ブラウザがiframe
、frame
、embed
、object
タグ内に埋め込まれたウェブサイトをレンダリングすることが許可されているかどうかを示すために使用されます。
もしCSPを設定している場合は、代わりにframe-ancestorsディレクティブを使用し、xFrame
ガードを無効にできます。
config/shield.ts
ファイルを使用してヘッダーディレクティブを設定できます。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
xFrame: {
enabled: true,
action: 'DENY'
},
})
-
enabled
-
xFrameガードをオンまたはオフにします。
-
action
-
action
プロパティはヘッダーの値を定義します。DENY
、SAMEORIGIN
、またはALLOW-FROM
のいずれかを指定できます。{action: 'DENY'}ALLOW-FROM
の場合、domain
プロパティも定義する必要があります。{action: 'ALLOW-FROM',domain: 'https://foo.com',}
MIMEスニッフィングの無効化
X-Content-Type-Optionsヘッダーは、ブラウザに対してcontent-type
ヘッダーに従い、HTTPレスポンスの内容を検査してMIMEスニッフィングを行わないよう指示します。
このガードを有効にすると、ShieldはすべてのHTTPレスポンスに対してX-Content-Type-Options: nosniff
ヘッダーを定義します。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
contentTypeSniffing: {
enabled: true,
},
})