@hapi/crumb

repository·master·Indexed 41 days ago

https://github.com/hapijs/crumb

A 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.

Tokens
2.5K
Snippets
5
Records
11
Agent score
48%

What's inside Crumb

  1. Overview of @hapi/crumb

    master
    @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.
  2. What is Crumb and how does it prevent CSRF attacks?

    master
    Crumb 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).
  3. Using Crumb with CORS

    master

    Crumb 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.origin to "["*]" or true while using Crumb. Doing so can leak the crumb token to potentially malicious sites.

  4. Register Crumb in a Hapi server

    master

    To 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');
          }
        }
      });
    })();
  5. Configure crumb validation for specific routes

    master

    You can override the global crumb settings for individual routes by using the plugins.crumb key 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: false in 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
                }
            }
        }
    });
  6. Configure Crumb plugin registration options

    master

    When registering the Crumb plugin, you can pass the following options to configure global behavior:

    OptionDescription
    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: Set secure: true for production.
    headerNameThe name of the custom CSRF header. Defaults to X-CSRF-Token.
    restfulIf true, validates tokens from the headerName for POST, PUT, PATCH, and DELETE routes, disabling payload/query validation. Defaults to false.
    skipA function function (request, h) {} that returns true to skip validation and generation. Defaults to false.
    enforceIf false, the CSRF header cookie is set but validation is not executed. Defaults to true.
    logUnauthorizedIf true, adds a log entry with tag 'crumb' and data 'validation failed' on failure. Defaults to false.
  7. Use RESTful mode for header-based CSRF validation

    master

    By default, @hapi/crumb expects 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: true in the plugin options or route configuration. When restful is enabled, the plugin looks for the crumb in the header specified by headerName (defaulting to X-CSRF-Token).

    Note: When restful is enabled, the plugin only validates POST, PUT, PATCH, and DELETE methods if a route-specific crumb configuration is present.

  8. Register the @hapi/crumb plugin

    master

    To use @hapi/crumb in 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 POST requests 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 Forbidden on 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();
  9. Configure @hapi/crumb options

    master

    When registering the plugin, you can pass an options object to customize its behavior. The following keys are available:

    KeyTypeDefaultDescription
    keystring'crumb'The name of the cookie used to store the crumb.
    sizenumber43The length of the generated crumb string.
    autoGeneratebooleantrueIf false, you must manually call request.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: true is configured.
    restfulbooleanfalseIf true, validation is performed via a custom header instead of the request payload/query.
    skipfunctionfalseA function (request, h) => boolean that, if returning true, skips crumb generation and validation.
    enforcebooleantrueIf true, validation is performed. If false, the cookie is set but validation is skipped (dry run).
    logUnauthorizedbooleanfalseIf true, an unauthorized attempt will write an event to the request log.
  10. Configure Crumb on a per-route basis

    master

    You can override global Crumb settings for specific routes using the plugins.crumb object in the route options. To completely disable Crumb for a route, pass false instead 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:

    OptionDescription
    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 restful setting. Defaults to plugin.restful.
  11. Manually generate a crumb with request.plugins.crumb.generate()

    master

    If autoGenerate is set to false in the plugin options, you must manually generate a crumb and attach it to the response. The plugin exposes a generate method via server.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 };
        }
    });