@hapi/crumb
repository·master·Indexed 41 days ago
https://github.com/hapijs/crumbA CSRF crumb generation and validation plugin for web applications. While designed for the hapi web framework, it is a standalone module that mitigates Cross-Site Request Forgery attacks by generating unique tokens stored in cookies and validating them on the server side. Version 9.0.1 supports both payload-based and RESTful header-based validation.
What's inside Crumb
- @hapi/crumb is a module designed for CSRF (Cross-Site Request Forgery) crumb generation and validation. While it is part of the hapi ecosystem and designed to work seamlessly with the hapi web framework, it is a standalone module that can be used with other web frameworks as well.
What is Crumb and how does it prevent CSRF attacks?
masterCrumb is a plugin used to mitigate Cross-Site Request Forgery (CSRF) attacks. It works by generating a random, unique token (a 'crumb') that is stored in a cookie and must be validated on the server side for state-changing requests. This prevents malicious code from executing unauthorized system commands via HTTP requests (e.g., via an HTML image tag in a user-generated content scenario).Using Crumb with CORS
masterCrumb is designed to work securely with Cross-Origin Resource Sharing (CORS). However, you must be careful with your server configuration:
Warning: It is highly discouraged to set your production server's
cors.originto"["*]"ortruewhile using Crumb. Doing so can leak the crumb token to potentially malicious sites.Register Crumb in a Hapi server
masterTo use Crumb, register it as a plugin during your Hapi server initialization. You can provide global plugin options during registration.
const Hapi = require('@hapi/hapi'); const Crumb = require('@hapi/crumb'); const server = new Hapi.Server({ port: 8000 }); (async () => { await server.register({ plugin: Crumb, // plugin options options: {} }); server.route({ path: '/login', method: 'GET', options: { plugins: { // route specific options crumb: {} }, handler(request, h) { // this requires to have a view engine configured return h.view('some-view'); } } }); })();Configure crumb validation for specific routes
masterYou can override the global crumb settings for individual routes by using the
plugins.crumbkey in the route configuration.When configuring a route, you can specify:
key: The key in the payload/query where the crumb is expected.restful: Whether to use header-based validation for this route.source: The source of the crumb in the request (defaults to'payload').
If you set
crumb: falsein the route plugins, crumb generation and validation will be disabled for that specific route.server.route({ method: 'POST', path: '/submit', options: { plugins: { crumb: { key: 'csrfToken', // The crumb must be present in request.payload.csrfToken restful: false } } } });Configure Crumb plugin registration options
masterWhen registering the Crumb plugin, you can pass the following options to configure global behavior:
Option Description keyThe name of the cookie to store the csrf crumb into. Defaults to crumb.sizeThe length of the crumb to generate. Defaults to 43(256 bits).autoGenerateWhether to automatically generate a new crumb for requests. Defaults to true.addToViewContextWhether to automatically add the crumb to view contexts as the given key. Defaults to true.cookieOptionsStorage options for the cookie (see hapi server.state()). Defaults to{ path: '/' }. Note: Setsecure: truefor production.headerNameThe name of the custom CSRF header. Defaults to X-CSRF-Token.restfulIf true, validates tokens from theheaderNamefor POST, PUT, PATCH, and DELETE routes, disabling payload/query validation. Defaults tofalse.skipA function function (request, h) {}that returnstrueto skip validation and generation. Defaults tofalse.enforceIf false, the CSRF header cookie is set but validation is not executed. Defaults totrue.logUnauthorizedIf true, adds a log entry with tag'crumb'and data'validation failed'on failure. Defaults tofalse.Use RESTful mode for header-based CSRF validation
masterBy default,
@hapi/crumbexpects the crumb to be inside the request payload (e.g.,request.payload.crumb).To use a custom header instead (common for AJAX/SPA applications), set
restful: truein the plugin options or route configuration. Whenrestfulis enabled, the plugin looks for the crumb in the header specified byheaderName(defaulting toX-CSRF-Token).Note: When
restfulis enabled, the plugin only validatesPOST,PUT,PATCH, andDELETEmethods if a route-specific crumb configuration is present.Register the @hapi/crumb plugin
masterTo use
@hapi/crumbin a Hapi server, register it as a plugin. You can provide a configuration object to customize how CSRF tokens (crumbs) are generated, stored, and validated.By default, the plugin:
- Uses a cookie named
crumb. - Automatically generates a new crumb if one doesn't exist.
- Validates crumbs for
POSTrequests by looking for a key in the request payload. - Adds the crumb to the view context if the response is a view.
- Enforces validation (throws
403 Forbiddenon failure).
const Hapi = require('@hapi/hapi'); const Crumb = require('@hapi/crumb'); const init = async () => { const server = Hapi.server({ port: 3000, host: 'localhost' }); await server.register({ plugin: Crumb, options: { // Configuration options go here } }); await server.start(); }; init();- Uses a cookie named
Configure @hapi/crumb options
masterWhen registering the plugin, you can pass an options object to customize its behavior. The following keys are available:
Key Type Default Description keystring'crumb'The name of the cookie used to store the crumb. sizenumber43The length of the generated crumb string. autoGeneratebooleantrueIf false, you must manually callrequest.plugins.crumb.generate()before usage.addToViewContextbooleantrueIf true, the crumb is added to the view context for template rendering.cookieOptionsobject{ path: '/' }Options passed to server.state()for the crumb cookie.headerNamestring'X-CSRF-Token'The name of the custom header used when restful: trueis configured.restfulbooleanfalseIf true, validation is performed via a custom header instead of the request payload/query.skipfunctionfalseA function (request, h) => booleanthat, if returningtrue, skips crumb generation and validation.enforcebooleantrueIf true, validation is performed. Iffalse, the cookie is set but validation is skipped (dry run).logUnauthorizedbooleanfalseIf true, an unauthorized attempt will write an event to the request log.Configure Crumb on a per-route basis
masterYou can override global Crumb settings for specific routes using the
plugins.crumbobject in the route options. To completely disable Crumb for a route, passfalseinstead of an object.```js server.route({ path: '/some-route', method: 'POST', options: { plugins: { crumb: { key: 'custom-key', source: 'payload', // or 'query' restful: true // override global restful setting } } } });Route Configuration Options:
Option Description keyThe key used in view contexts and payloads. Defaults to the plugin's key.sourceSpecifies how the crumb is sent: 'payload'or'query'. Defaults to'payload'.restfulOverrides the server's global restfulsetting. Defaults toplugin.restful.Manually generate a crumb with request.plugins.crumb.generate()
masterIf
autoGenerateis set tofalsein the plugin options, you must manually generate a crumb and attach it to the response. The plugin exposes ageneratemethod viaserver.expose.Inside a handler, you can call
request.plugins.crumb.generate(request, h)to create a new crumb, set the cookie, and return the value.server.route({ method: 'GET', path: '/get-token', handler: async (request, h) => { // Manually generate the crumb const token = await request.plugins.crumb.generate(request, h); return { token }; } });