Git Product home page Git Product logo

crumb's Introduction

@hapi/crumb

CSRF crumb generation and validation for hapi.

crumb is part of the hapi ecosystem and was designed to work seamlessly with the hapi web framework and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out hapi โ€“ they work even better together.

Visit the hapi.dev Developer Portal for tutorials, documentation, and support

Useful resources

crumb's People

Contributors

aef- avatar akanass avatar alonmiz avatar arb avatar benhoiiand avatar bodawei avatar briandela avatar cjihrig avatar devinivy avatar dnecklesportfolio avatar eiriksm avatar geek avatar gyaresu avatar hueniverse avatar jarrodyellets avatar jonathansamines avatar ldesplat avatar lloydbenson avatar marsup avatar mcandre avatar michelrossier avatar mtharrison avatar nargonath avatar nlf avatar sher avatar spanditcaa avatar stongo avatar thegoleffect avatar tomsteele avatar tuckbick avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

crumb's Issues

Provide more relevant feedback

It would be nice if a message was provided to the Boom.forbidden. In big applications, where Boom is used everywhere, it may not be trivial to detect where the error was generated.

Question: Was it intentional to validate crumb key for POST only?

Hi,

I've noticed that Crumb skips crumb validation for PUT/DELETE (or anything other than POST). (see crumb/index.js line 58)

Was this intentional?

-fd

(Use of PUT / DELETE can be misused, which I am not ashamed to do, and just noticed that crumb wasn't validating csrf token on my PUT requests.)

Undefined crumb value get in jade template

I'm using crumb plugin in my project. I haven't set any options. I try to get crumb value in jade template in this way

input(type='hidden', name='crumb', value='#{crumb}')

but the value is undefined. Why? Should I set something?

The ttl of the crumb cookie

Hi,

I'm testing crumb currently and have one question.

  1. I've configured crumb in a simple way, so all my POST/PUT etc routes protected by the X-CSRF-Token header.
    image
  2. I've tested it against the POST route and got it working.
    image

BUT, the crumb cookie has still the same value. Could you clarify - how long could I use the same crumb cookie? When would it be regenerated by the server and if there would be such moment sometime? Or it would be valid till I would not close the browser.

Are there any configuration options to impact on the cookie regeneration behaviour?

Regards,

Generate is running a second time and 500-ing hapi

possibly related to #54
For a restful hapi pipeline, with the conf below, after I generate a crumb and get it back as a cookie. When I submit a data request I get

Debug: internal, implementation, error 
    TypeError: Uncaught error: Cannot read property 'crumb' of null
    at generate (/Users/$ME/Developer/pipeline/node_modules/crumb/lib/index.js:159:34)

The index.js of the hapi server asks for this:

server.register(
        {
            register: require('crumb'),
            options: {
                restful: true
            }
        },
        (err) => {
            if (err) {
                throw err;
            }
        }
    );

and the restful requests are

const env = require("../../config/environment"),
    httpreq = require('request');
module.exports = [
    {
        method: 'GET',
        path: '/generate',
        config: {
          auth: false
        },
        handler: function (request, reply) {
            return reply(
                { crumb: request.server.plugins.crumb.generate(request, reply) }
            );
        }
    },
    {
        method: 'PUT',
        path: '/crumbed',
        handler: function (request, reply) {
            console.log('crumb put has happened')
            console.log(request);
            return reply('../../node_modules/crumb/lib/index.js');
                   // it says crumb route, meaning this? 
        }
    }
];

(fun fact, I never see 'crumb put has happened' when PUT-ing to /crumbed, the 500 previously mentioned hits first).

Is this a bug with the generate function, or have I just royally failed to configure crumb?

Prepare for hapi v6.0

hapijs/hapi#1664

Just need to add attributes. For full migration to v6.0 look at the hapi6 branch (not ready for integration as hapi v6.0 has not been published, but adding attributes now is safe).

Question: Does it make sense to use Crumb with CORS?

Hi,

I may be completely misunderstanding the code and/or XSRF protection in general but...

In 3.x.x. if CORS is enabled, Crumb never generates a crumb cookie. See:

!request.server.settings.cors) {

All requests pass through the plugin with no XSRF protection, even though I am using the plugin.

Does this mean that Crumb should not be used in an environment supporting CORS?

(For background, previously with Crumb 2.2.0, I had CORS set up, was using Crumb restful=false, and passing XSRF tokens in the payload. Which was working great. This no longer works with Crumb 3.x.x. With my same 2.2.0 setup, Crumb doesn't do anything. I was surprised to discover this. Again, perhaps I'm misunderstand XSRF protection when using CORS.)

Thanks
fd

Getting 403 forbidden because token has changed

Hi guys, I'm working in a HapiJS API and a separate Web client app (Just HTML and JS) which basically will have a form with a CSRF crumb token. So the Web app basically makes a call to GET /generate and HapiJS will return the CSRF token. After that when I submit the form I hit POST /send and sends the token with X-CSRF-Token header but for some reason is returning 403 ...

HAPIJS Routes

server.route([
        {
            method: 'GET',
            path: '/generate',
            handler: Controllers.Auth.generate_csrf,
            config: {
                auth: false,
                jsonp: 'callback'
            }
        },
        // request header "X-CSRF-Token" with crumb value must be set in request for this route
        {
            method: 'POST',
            path: '/send',
            handler: Controllers.Auth.csrf_handshake,
            config: {
                auth: false
            }
        }
    ]);

HapiJS routes handlers:

exports.generate_csrf = function (request, reply) {
    request.log.info(request.info, 'Request to generate CSRF Token');
    return reply({csrf_token: request.plugins.crumb});
};

exports.csrf_handshake = function (request, reply) {
   return reply('OK');
};

On the web side:

(function () {

    var form = $("#sms-form");
    var host = "http://localhost:3002"; // HAPIJS Host

    var getAuth = function () {
        $.ajax({
            url: host + "/generate",
            jsonp: "callback",
            dataType: "jsonp",
            success: function( response ) {
                var token = response.csrf_token; 
                var input = $('<input name="csrf_token" type="hidden" value="'+token+'">');
                form.append(input);
            },
            error: function (req, status, err) {
                console.log('error');
            }
        });
    };

    getAuth();


    form.on('submit', function (e) {
        e.preventDefault();

        var authorizationToken = $(e.currentTarget).find('input[name="csrf_token"]').val();

        console.log(authorizationToken);
        if(authorizationToken) {
            $.ajax({
                type:"POST",
                beforeSend: function (request)
                {
                    request.setRequestHeader("X-CSRF-Token", authorizationToken);
                },
                url: host + "/send",
                data: {crumb: authorizationToken},
                dataType: "json",
                success: function(msg) {
                    console.log(msg);
                }
            });
        }else {
            console.log("We're having issues this request");
        }


    });

})();

Here's plugin config:

{
register: require('crumb'),
options: { restful: false }
}

Also I noticed that when calling multiple times GET /generate is returning the same CSRF token, not sure how to generate a new one per request.

When adding a debugger to crumb/lib/index.js, I noticed that the they're not the same:

          debugger;
            if (content instanceof Stream) {

                return reply(Boom.forbidden());
            }

            if (content[request.route.settings.plugins._crumb.key] !== request.plugins.crumb) {
                return reply(Boom.forbidden());
            }

Am I missing something here ?

ES6 style changes and node v4

Apply the latest ES6 hapi style:

  • use strict
  • const and let where appropriate (linter will tell you where)
  • arrow functions where appropriate (linter will tell you where)
  • relevant updates to tests + docs
  • updating deps and engine in package.json
  • avoid self and += / -= (not optimized yet for let const)
  • change .travis to test 4.0, 4, and 5

No crumb cookie using restful example

I have tried running the restful.js example but I get no crumb cookie on the response headers as stated.

// a "crumb" cookie gets set with any request when not using views

I am able to get the crumb as payload on the response of /generate but no cookie.

Ignore CORS

I don't know why the change was made to skip crumb protection when CORS is enabled but please revert that change. While theoretically, a properly configured CORS deployment does not need much of this extra protection, this is a violation of security layering principle.

Also, almost no one configures CORS correctly.

Should err when trying to parse from a buffer

Turns out a problem I was having with crumb (it's now fixed - yay!) was that I had the following route:

  facet.route({
    path: "/star",
    method: "POST",
    config: {
      handler: require('./show-star'),
      payload: { parse: false },
      plugins: {
        crumb: {
          source: 'payload',
          restful: true
        }
      }
    }
  });

Note the payload: { parse: false } - the payload came through the route as a buffered stream instead of an object, which would cause content[request.route.plugins._crumb.key] to be undefined, and thus render https://github.com/hapijs/crumb/blob/master/lib/index.js#L88 as true. (As a result, I kept getting a 403 on the route.)

Instead, crumb should notify the user that the stream is a buffer with an error indicating such, so that the user can make sure the payload is a nice pretty object instead ;-)

Publish to npm

Could you publish a hapi 8.0 compatible version to npm please?

Heroku disallows host binding but Crumb requires it

I'm stuck in a situation where I need to deploy to Heroku which disallows the binding of a host name (doesn't bind/mount) but Crumb requires I bind to a host in order to work.

Is there a solution to this type of catch-22?

Just a random question about example/restful.js

What exactly are the paths /generate and /crumbed? ...I'm not exactly sure how to (or whether or not to) change the paths in the example, or if we always need to go to '/generate' to get a crumb-token, or if we should be generating a new token at every new path we arrive at, etc. I'm also not sure what exactly '/crumbed' is supposed to be, or how exactly the PUT method in the example checks for a "X-CSRF-Token" in the request header... I'm sorry, I'm new to Nodejs and have been trying to dissect your code and integrating it with https://github.com/jedireza/frame.

Documentation

Could you please add some documentation and/or examples?

Persist csrf-tokens and share them between servers?

Please let me know if I have understood this correctly:

  • Crumb generates csrf-tokens and persist them as a cookie on the browser and in-memory on the server.
  • Crumb doesn't persist tokens to db/cache/elsewhere by default, so it is intended for single-server use.
    If we'd like to divide web traffic between multiple servers, we need to persist the tokens somewhere so they're available to all servers.

Tests fail on v6.0.2

Running the tests on master fail, giving me the following error. I get the same error on both node v4 and v6, but not on the tag v6.0.1.

  this.push is not a function

  at TestStream._read (crumb/test/index.js:146:46)
  at Readable.read (_stream_readable.js:336:10)
  at resume_ (_stream_readable.js:733:12)
  at nextTickCallbackWith2Args (node.js:442:9)
  at process._tickDomainCallback (node.js:397:17)

I am not sure why the tests on Travis passed yet are failing for me, but I have attempted this in multiple environments. It looks like this is never assigned as arrow functions are used all of the way up to the root.

Any ideas?

Check number of connections

When using this plugin with server.plugin(... , { select: 'public' }), and that no connection matches the selection, then an error is returned:

Error: Cannot add state without a connection

Is this a desired behavior? Or is it expected that server.select() may return an empty set?

Restful example generate endpoint responds with text/html response

Just wondering if there's any good reason that the restful example /generate endpoint responds with text/html and a JSONy looking string:

server.route({
    method: 'GET',
    path: '/generate',
    handler: function (request, reply) {

        // return crumb if desired
        return reply('{ "crumb": ' + request.plugins.crumb + ' }');
    }
});

rather than:

server.route({
    method: 'GET',
    path: '/generate',
    handler: function (request, reply) {

        // return crumb if desired
        return reply({ crumb: request.plugins.crumb });
    }
});

Happy to make a PR if you think it's worth it.

Missing milestones

You need to do a better job at keeping track of changes and milestones. There is a single milestone but multiple releases. Also, current package.json is set to 4.0 without any explanation.

update to hapi 9/10

Update crumb to be compatible with hapi 9, and ideally with hapi 10/node 4.x

Disable crumb per route

Hi,

Is there a way to disable the crumb through the route configuration?

In the documentation I see that we can do like this:

options: {
    restful: true,
    cookieOptions: {
        ttl: 86400000
    },
    skip: function(request, reply) {
        // to disable it for the save-email method
        if (request.path === 'save-email') {
            return true;
        }
    }
}

This mean that we should describe all the routes which we want to "decrumbialise" during the plugin registration. Am I right?

Generate function never called when Vision route has CORS enabled

So I think found the reason why the crumb cookie isn't being set sometimes.

If you setup your server with CORS enabled globally but also using Vision like this:

const server = new Hapi.Server({
    connections: {
      routes: {
        cors: true
      }
    }
});
server.connection();
server.register([{
    register: require('vision'),
    options: Config.vision
}, {
    register: require('crumb'),
    options: Config.crumb
}])
...

And then proceed to create a few routes returning views, the view routes will never call Crumb's generate function, because https://github.com/hapijs/crumb/blob/master/lib/index.js#L83 will always fail unless CORS is explicitly turned off for the view route.
The reason being that request.route.settings.cors evaluates to true, but then no CORS headers are actually set with the view, so the origin header isn't set making request.info.cors.isOriginMatch fail here https://github.com/hapijs/hapi/blob/ed195fad213a9da0f0762271c4907f4218e2abaf/lib/cors.js#L177-L179

As far as I can see, it comes down to the user being aware that a view route can't have CORS enabled. @hueniverse do you think there's any way to work around this in code, or will the best solution be to document the heck out of it in Crumb?

Forbidden 403 when make cross origin requests

I have set cors to true and these are the options I'm using for the plugins:

{
  plugin: require('crumb'),
  options: {
    cookieOptions: {
      isSecure: false
    },
    allowOrigins: ['*.sendgrid.com']
  }
}

I'm using sendgrid nodejs official plugin and I make an ajax request

$.post('/server-url', data);

but I get an error. Why? is it a bug or I should set something?

Overriding restful options in route

I noticed that overriding the default restful option (false), in routes does not work. Overriding only seems to work when restful is globaly set to true. I think the problem is a short-circuit evaluation in the if statement when branching to validate either by content or by x-csrf-token header:
if (settings.restful === false || (!request.route.settings.plugins._crumb || request.route.settings.plugins._crumb.restful === false)){
I thought this behaviour is not quite what you would expect.

Validate/Requiring an incoming crumb even if CORS origin does not match

I wanted to get your thoughts on validating/requiring an incoming crumb value (cookie/payload), even if the CORS origin does not match.

I know setting a crumb cookie when the CORS origin does not match is considered token leaking but would it be possible to require the existence of a crumb to be present and that crumb to be valid?

For example, here are some scenarios (how it works today):

  • with cors disabled
    • if I don't have a crumb or it's not valid, the request will 403
  • with cors enabled and a matching cors origin
    • if I don't have a crumb or it's not valid, the request will 403
  • with cors enabled, and a non-matching origin
    • the presence or lack of crumb doesn't make any difference and the request handler will run normally, as in today's code, generateCrumb is not called and it is critical to the validation flow.

Would it be possible to change the code flow so that in the final scenario, the with cors enabled, and a non-matching origin, the code still checks for a valid crumb? I believe it would involve refactoring the code a bit to separate the crumb validation from the generate call; it looks like today that generate has two responsibilities:

  1. read the current crumb cookie and if it exists, set the value of it to request.plugins.crumb (the existence of request.plugins.crumb is later used to determine if a check should even happen on the crumb)
  2. read the current crumb cookie, and if it does not exist, generate a crumb, and set a cookie for the response using reply.state, and set the value to request.plugins.crumb

When the CORS origins do not match, 2. above is the potential for leaking the CSRF cookie as it would set a crumb, but I also think that 1. could be achieved independently of 2., by not having generate have multiple responsibilities, and thus still validate CSRF for non-matching CORS origins?

Thoughts on this @stongo? I'll gladly submit a PR if you think it would be an acceptable way forward.

crumb request.plugins.crumb: undefined

Versions: HAPI: 8.5.2, crumb: 4.0.3, hapi-auth-cookie: 2.2.0
Startup process:

crumbOpts = {
            key: 'encrumb',
            restful: true,
            allowOrigins: ['http://*.emprego.net', 'https://*.emprego.net'],
            cookieOptions: {
                ttl: 356 * 60 * 60 * 1000,
                isSecure: false
            }
        };

authOpts = {
            password: 'anynicepassword',
            cookie: 'sid-myappname',
            redirectTo: '/unlogged',
            isSecure: false,
            keepAlive: true,
            ttl: 356 * 60 * 60 * 1000
        };

I am staring up first Crumb and then Cookie.

On my machine (Mac OSx Yosemite) I start the server with pm2 with several CPUs. It all works fine.

On the production server I start the server with pm2 with several CPUs. It all works fine with Chrome and IE. Once I use Firefox it is giving me the following problem:

It is generating the crumb just fine.

ui-0 (out): crumb generate --> crumb: xn27IZhk_WxKw0xSFVdWH2wbuWVoawsKWYWm0hPxM7Z
ui-0 (out): crumb onPreResponse --> request.plugins.crumb: xn27IZhk_WxKw0xSFVdWH2wbuWVoawsKWYWm0hPxM7Z
ui-0 (out): crumb onPostAuth
ui-0 (out): crumb onPostAuth 1 - request.route.settings.plugins._crumb: [object Object]
ui-0 (out): crumb onPostAuth settings.autoGenerate: true request.route.settings.plugins._crumb: [object Object]
ui-0 (out): crumb onPreResponse --> settings.addToViewContext: true - request.route.settings.plugins._crumb: [object Object] - request.plugins.crumb: undefined - response.variety: view

But once I make a POST request:

ui-0 (out): crumb onPostAuth
ui-0 (out): crumb onPostAuth settings.autoGenerate: true request.route.settings.plugins._crumb: [object Object]
ui-0 (out): crunb onPostAuth --> header: xn27IZhk_WxKw0xSFVdWH2wbuWVoawsKWYWm0hPxM7Z
ui-0 (out): 'xn27IZhk_WxKw0xSFVdWH2wbuWVoawsKWYWm0hPxM7Z'
ui-0 (out): crunb onPostAuth --> request.plugins.crumb: undefined
ui-0 (out): crumb onPreResponse --> settings.addToViewContext: true - request.route.settings.plugins._crumb: [object Object] - request.plugins.crumb: undefined - response.variety: undefined

Notice that request.plugins.crumb: undefined
is undefined

I have tried all sorts of ways to detect why this might be happening, but have not yet come to any light on how to possibly solve this problem.

how to use braintree in hapijs

tmacie

Can you tell me how to integrate braintree in hapijs, or can please you write a tutorial on "how to integrate braintree in hapijs" and how to use it.

Add skip crumb validation function

It would be great to be able to provide a function that is evaluated prior to validating the crumb. The use case is having a API used by various sources outside of the browser, but still wanting to use that API in the browser and have it be protected from CSRF. Application consumers wouldn't want to have to generate a csrf token when authenticating outside of a cookie, say they use a Authorization header or maybe 'X-API-Token', etc.

I am thinking a setting called skip which defaults to false, but can be provided as a function which when returns true, allows the validation function to return early.

plugin.ext('onPostAuth', function (request, reply) {

    // If skip function enabled. Call it and if returns true, do not attempt to do anything with crumb.
    if (settings.skip && typeof settings.skip === 'function' && settings.skip(request, reply)) {
        return reply();
    }

Forbiden when making a RESTful(ish) POST

Hi,
I am still kind of new to Hapi and the eco system.
I had got crumb to work with the normal POST calls.

But now I am changing my architecture, I have the API and UI server connections.
The UI talks to API strictly from the server side.

Now, when I pass the POST request to the API from the server I get forbiden,
But when I POST directly from the form page, everything works as expected.

I think it because the I dont pass the crumb value with the POST call.
I have tried several things trying to fix this
eg req.post('host').set({'crumb': request.plugins.crumb }).post({information}) ...
but nothing seems to work.

Thanks for help in understanding better this stuff.

Question: Same-domain requests are blocked when cors is enabled?

I have a server set up at http://somedomain.com. I have enabled cors on a specific route:

{
  method: 'GET',
  path: '/generate',
  config: {
    cors: {
      origin: ['http://otherdomain.com:3000']
    }
  },
  handler: ......

Ajax-requests from http://otherdomain.com:3000 works fine and Crumb generates a token as expected.

But if I do an ajax-request from http://somedomain.com (the same domain the server is running on) to this route, Crumb doesn't generate a token.

It seems like enabling cors implies that only cross-domain requests are allowed? Is this correct?

Cannot read property crumb of null

Hello! For some reason, some of my requests are returning a null object for the value of requests.state, resulting in the following error:

Stack trace
TypeError: Cannot read property 'crumb' of null
at generate (/usr/www/440/node_modules/crumb/lib/index.js:156:34)
at /usr/www/440/node_modules/crumb/lib/index.js:85:13
at /usr/www/440/node_modules/hapi/lib/handler.js:312:22
at iterate (/usr/www/440/node_modules/hapi/node_modules/items/lib/index.js:35:13)
at Object.exports.serial (/usr/www/440/node_modules/hapi/node_modules/items/lib/index.js:38:9)
at /usr/www/440/node_modules/hapi/lib/handler.js:307:15
at internals.Protect.run (/usr/www/440/node_modules/hapi/lib/protect.js:56:5)
at Object.exports.invoke (/usr/www/440/node_modules/hapi/lib/handler.js:305:22)
at /usr/www/440/node_modules/hapi/lib/request.js:318:32
at iterate (/usr/www/440/node_modules/hapi/node_modules/items/lib/index.js:35:13)
at done (/usr/www/440/node_modules/hapi/node_modules/items/lib/index.js:27:25)
at finish (/usr/www/440/node_modules/hapi/lib/protect.js:45:16)
at wrapped (/usr/www/440/node_modules/hapi/node_modules/hoek/lib/index.js:798:20)
at done (/usr/www/440/node_modules/hapi/node_modules/items/lib/index.js:30:25)
at Function.wrapped [as _next] (/usr/www/440/node_modules/hapi/node_modules/hoek/lib/index.js:798:20)
at Function.internals.continue (/usr/www/440/node_modules/hapi/lib/reply.js:102:10)

Any ideas as to what might cause request.state to be null from hapi? I checked my other plugins and haven't found anything that might unset state generally.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.