node-restify: OPTIONS does not work with CORS by default

I received the error message:

405 (Method Not Allowed)

when making an OPTIONS request. Backbone does this by default.

There seems to be lots of discussion about it in issue #284, but I didn’t find a solution that worked with node-restify 2.8.2.

The code I am using is:

server.pre(restify.CORS());
server.use(restify.fullResponse());

What is the current recommended solution?

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Reactions: 2
  • Comments: 20 (6 by maintainers)

Commits related to this issue

Most upvoted comments

Thanks @Qwerios This is my configuration in order to make angular-jwt work with restify.

app.pre(restify.CORS({
  origins: ['http://localhost:3000'],
  credentials: false,
  headers: ['authorization']
}));

restify.CORS.ALLOW_HEADERS.push("authorization");
app.on( "MethodNotAllowed", function(req, res) {
  if(req.method.toUpperCase() === "OPTIONS" ) {
    // Send the CORS headers
    res.header("Access-Control-Allow-Headers", restify.CORS.ALLOW_HEADERS.join( ", " ));
    res.send(204);
  }
  else {
    res.send(new restify.MethodNotAllowedError());
  }
});

I hope it may be helpful for someone.

I’ll add here the code I’m now adding to each of my API projects to get CORS to work. Feel free to use or abuse it at your leisure…

...
server.use( restify.CORS() );
...

// Lets try and fix CORS support
// By default the restify middleware doesn't do much unless you instruct
// it to allow the correct headers.
//
// See issues:
// https://github.com/mcavage/node-restify/issues/284 (closed)
// https://github.com/mcavage/node-restify/issues/664 (unresolved)
//
// What it boils down to is that each client framework uses different headers
// and you have to enable the ones by hand that you may need.
// The authorization one is key for our authentication strategy
//
restify.CORS.ALLOW_HEADERS.push( "authorization"        );
restify.CORS.ALLOW_HEADERS.push( "withcredentials"      );
restify.CORS.ALLOW_HEADERS.push( "x-requested-with"     );
restify.CORS.ALLOW_HEADERS.push( "x-forwarded-for"      );
restify.CORS.ALLOW_HEADERS.push( "x-real-ip"            );
restify.CORS.ALLOW_HEADERS.push( "x-customheader"       );
restify.CORS.ALLOW_HEADERS.push( "user-agent"           );
restify.CORS.ALLOW_HEADERS.push( "keep-alive"           );
restify.CORS.ALLOW_HEADERS.push( "host"                 );
restify.CORS.ALLOW_HEADERS.push( "accept"               );
restify.CORS.ALLOW_HEADERS.push( "connection"           );
restify.CORS.ALLOW_HEADERS.push( "upgrade"              );
restify.CORS.ALLOW_HEADERS.push( "content-type"         );
restify.CORS.ALLOW_HEADERS.push( "dnt"                  ); // Do not track
restify.CORS.ALLOW_HEADERS.push( "if-modified-since"    );
restify.CORS.ALLOW_HEADERS.push( "cache-control"        );

// Manually implement the method not allowed handler to fix failing preflights
//
server.on( "MethodNotAllowed", function( request, response )
{
    if ( request.method.toUpperCase() === "OPTIONS" )
    {
        // Send the CORS headers
        //
        response.header( "Access-Control-Allow-Credentials", true                                    );
        response.header( "Access-Control-Allow-Headers",     restify.CORS.ALLOW_HEADERS.join( ", " ) );
        response.header( "Access-Control-Allow-Methods",     "GET, POST, PUT, DELETE, OPTIONS"       );
        response.header( "Access-Control-Allow-Origin",      request.headers.origin                  );
        response.header( "Access-Control-Max-Age",           0                                       );
        response.header( "Content-type",                     "text/plain charset=UTF-8"              );
        response.header( "Content-length",                   0                                       );

        response.send( 204 );
    }
    else
    {
        response.send( new restify.MethodNotAllowedError() );
    }
} );

I’m facing this problem with Angular’s $http, and restify v2.8.4.

OPTIONS /auth HTTP/1.1
    host: localhost:7777
    connection: keep-alive
    pragma: no-cache
    cache-control: no-cache
    access-control-request-method: POST
    origin: http://localhost:9000
    user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
    access-control-request-headers: accept, authorization, content-type
    accept: */*
    referer: http://localhost:9000/
    accept-encoding: gzip, deflate, sdch
    accept-language: en-US,en;q=0.8,en-GB;q=0.6

HTTP/1.1 405 Method Not Allowed
    allow: POST
    content-type: application/json
    content-length: 67

I am also using:

server.use(restify.CORS());
server.use(restify.fullResponse());

and this solves the problem:

restify.CORS.ALLOW_HEADERS.push('authorization');

Is this the recommended solution? Is there a reason this isn’t enabled by default?

Michael, feel free to submit a PR Iinstead of raging about an open source framework maintained by folks for free.

On Tuesday, October 6, 2015, Michel Boudreau notifications@github.com wrote:

How is this still not fixed? It should be marked as a bug, and a major one at that. When you enable CORS, I’d expect it to work like any other HTTP servers out there. I just want it to accept external request, that’s all (no matter which headers are sent).

Even with the fix suggested above, I’ve removed all headers (used postman) and only had an OPTIONS request to the server and still had an “MethodNotAllowedError” while CORS and fullResponse enabled.

— Reply to this email directly or view it on GitHub https://github.com/restify/node-restify/issues/664#issuecomment-146064075 .

Via mobile

The problem seems to be that Backbone sends a couple extra headers that broke the CORs preflight.

I solved the problem by using:

restify.CORS.ALLOW_HEADERS.push('Accept-Encoding');
restify.CORS.ALLOW_HEADERS.push('Accept-Language');

server.use(restify.CORS())

Shame CORs is not more developer friendly. I had to fully understand CORs before I could solve it.

Express didn’t have this problem. Perhaps they add more headers by default? Perhaps that is worth considering for other restify developers?

The CORS module has found a new home and has been updated to be both correct and stable 😄

https://github.com/TabDigital/restify-cors-middleware/

I believe the recent changes address this issue, if I’m missing a nuance here please comment and I’d be happy to re-open this issue to discuss. We could also migrate this issue over to the new repo if needed ❤️

Better solution than having an event on top of MethodNotAllowed (Just if you don’t need to deploy any route on OPTIONS method).

  app.use(
    restify.CORS({
      origins: [
        'http://development.example.com',
        'https://staging.example.com',
        'https://www.example.com',
      ],
      headers: [
        "authorization",
        "withcredentials",
        "x-requested-with",
        "x-forwarded-for",
        "x-real-ip",
        "x-customheader",
        "user-agent",
        "keep-alive",
        "host",
        "accept",
        "connection",
        "upgrade",
        "content-type",
        "dnt",
        "if-modified-since",
        "cache-control"
      ]
    })
  )

// Handle all OPTIONS requests to a deadend (Allows CORS to work them out)
app.opts( /.*/, ( req, res ) => res.send( 204 ) )