hapijs / crumb Goto Github PK
View Code? Open in Web Editor NEWCSRF crumb generation and validation for hapi
License: Other
CSRF crumb generation and validation for hapi
License: Other
Almost done, just tracking this
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?
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.
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 ?
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();
}
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.
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?
need to verify hapi 5 compatibility
This will allow Crumb to work out of the box with CORS, but will still allow finer grained control of setting the crumb cookie to allowed CORS origins if desired.
Hi...
Currently 3.1.0 is available via npm but not tagged as released.
See here:
https://github.com/hapijs/crumb/releases.
And comment here:
https://github.com/hapijs/crumb/blob/master/lib/index.js#L52
restful: settings.resful,
should be..
restful: settings.restful,
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.
This will cause unpredictable errors when loading the plugin multiple times with different configurations to different connections.
Could you publish a hapi 8.0 compatible version to npm please?
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?
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?
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):
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:
request.plugins.crumb
(the existence of request.plugins.crumb
is later used to determine if a check should even happen on the crumb)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.
Please let me know if I have understood this correctly:
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.)
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?
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.
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 ;-)
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?
Hi,
I'm testing crumb currently and have one question.
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,
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.
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?
I have cors enabled and I'm using this options
plugin: require('crumb'),
options: {
cookieOptions: {
isSecure: false
},
allowOrigins: ['sendgrid.com:*']
}
}
I'm using sengrid official nodejs API and if I put cors false, it doesn't work. I need cors true
Could you please add some documentation and/or examples?
I want CSRF protection when authenticated by cookies but not when authenticated by hawk.
Add an Origin check. If the origin doesn't match the server name and CORS is enabled, crumb validation should be bypassed. This should handle instances when CORS is enabled, but same origin calls are still made to the server.
Token leakage acceptable if cors.origin set to specified hosts
Update tests according to this
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.
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.
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:
Line 59 in b019180
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
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.
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.
I would like to be able to turn off crumb on a per-route basis. I'm adding routes to my app for handling Braintree webhooks (https://developers.braintreepayments.com/javascript+node/guides/webhooks) and they will not have the CSRF token. Thoughts?
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.
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?
Apply the latest ES6 hapi style:
const
and let
where appropriate (linter will tell you where)self
and +=
/ -=
(not optimized yet for let const)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.
Update crumb to be compatible with hapi 9, and ideally with hapi 10/node 4.x
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).
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.