Allow multiple headers with CORS in Suave
Asked Answered
S

2

6

I'm trying to get my Suave API to accept CORS requests. I've followed this snippet here:

http://www.fssnip.net/mL/title/CORS-response-with-Suave

Which I will recreate here:

let setCORSHeaders =
    setHeader  "Access-Control-Allow-Origin" "*"
    >=> setHeader "Access-Control-Allow-Headers" "content-type"

let allow_cors : WebPart =
    choose [
        OPTIONS >=>
            fun context ->
                context |> (
                    setCORSHeaders
                    >=> OK "CORS approved" )
    ]

let webSite =
    choose [
        allow_cors
        GET >=> OK "URLs are for wimps. GETting something? This is what you get."
    ]

But now I have endpoints that require a token to be passed in, and those endpoints are giving errors due to missing allowed headers in CORS. My token header is simply "token", so I've tried two things, and both have not resolved the issue.

Attempt #1

    setHeader  "Access-Control-Allow-Origin" "*"
    >=> setHeader "Access-Control-Allow-Headers" "content-type"
    >=> setHeader "Access-Control-Allow-Headers" "token"

This returned an error saying content-type was no longer accepted - this seems to imply that the last setHeader overwrote the first one, which when you view the source code here : https://github.com/SuaveIO/suave/blob/master/src/Suave.Tests/HttpWriters.fs , at line 113, there is a test that to me implies this is the desired behavior.

Attempt #2

Based on the response on this question: How to set a Json response in suave webpart, I attempted to set both headers via a comma-separated list:

    setHeader  "Access-Control-Allow-Origin" "*"
    >=> setHeader "Access-Control-Allow-Headers" "Content-Type,token"

But this then gave an error indicating that CORS was failing entirely:

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access. The response had HTTP status code 401.

Also based on this same question, I tried the following:

    setHeader  "Access-Control-Allow-Origin" "*"
    >=> setHeader "Access-Control-Allow-Headers:content-type" "Accept"
    >=> setHeader "Access-Control-Allow-Headers:token" "Accept"

Which gave this response: Request header field token is not allowed by Access-Control-Allow-Headers in preflight response.

So, how do I allow any amount of headers in a CORS request in Suave?

Edit

Attempt #3

I used addHeader instead of setHeader:

let setCORSHeaders =
    setHeader  "Access-Control-Allow-Origin" "*"
    >=> addHeader "Access-Control-Allow-Headers" "content-type"
    >=> addHeader "Access-Control-Allow-Headers" "token"

Which is now saying... No 'Access-Control-Allow-Origin' header is present on the requested resource.

If I change that first setHeader to addHeader, I still get the same response.

Fix

    addHeader  "Access-Control-Allow-Origin" "*" 
    >=> setHeader "Access-Control-Allow-Headers" "token" 
    >=> addHeader "Access-Control-Allow-Headers" "content-type" 
    >=> addHeader "Access-Control-Allow-Methods" "GET,POST,PUT" 

Did the job - after this, there were issues with what was being sent, but nothing more with CORS itself.

Stencil answered 4/6, 2017 at 22:23 Comment(0)
S
5

Try using addHeader instead of setHeader. Just a few lines further down in the unit tests that you found, there's a test for addHeader that shows that it has the semantics you're looking for, and its documentation says:

Adds the header key with the given value to the list of returned headers, even if that header already exists. This means that Suave will serve a a response with the header denoted by key with possibly different values.

This looks like the behavior you're wanting.

Shatter answered 5/6, 2017 at 0:34 Comment(4)
This made perfect sense and was easy to apply, so naturally it's still broken :(. Please see my edit above.Stencil
html5rocks.com/en/tutorials/cors says that Access-Control-Allow-Methods is also required; are you setting that anywhere? If not, it could be that the real problem is the missing Access-Control-Allow-Methods header, and the error message is misleading. But I'm only guessing at this point. I'd say read html5rocks.com/en/tutorials/cors and keep experimenting with different header combinations until you find the correct set of CORS headers. Sorry I can't be of more help than that.Shatter
This lead me in the right direction - and it turns out there is a whole Suave.CORS namespace I could have used the whole time, which naturally I didn't find until I had worked past this issue.Stencil
Well, now you know how to do it manually -- which means you'll have a better understanding of what the Suave.CORS functions are doing behind the scenes. That's usually a good thing, since when you hit a snag in the future you'll be better equipped to deal with it. It's the same idea as the Linux From Scratch project: learn to do it the hard way, then when you use the easy way you have more understanding of what's going on and you can fix it if it breaks.Shatter
R
2

I had a similar problem. Due to legacy reasons I'm stuck for the moment with verion 1.1.3 even though Suave 2.2.1 is available. I'm not really familiar with F# but eventually I managed to get this working with somehting like this:

let setCORSHeaders =
    addHeader  "Access-Control-Allow-Origin" "*" 
    >=> setHeader "Access-Control-Allow-Headers" "token" 
    >=> addHeader "Access-Control-Allow-Headers" "content-type" 
    >=> addHeader "Access-Control-Allow-Methods" "GET,POST,PUT" 

let app =
    choose [
        GET >=>
            fun context ->
                context |> (
                    setCORSHeaders
                    >=> choose
                        [ pathRegex "(.*?)\.(dll|mdb|log)$" >=> dllFilesRequest
                        pathRegex "(.*?)\.(html|css|js|png|jpg|ico|bmp)$" >=> staticFilesRequest

                        path "/" >=> indexRequest
                        path "/index" >=> indexRequest
                        path "/static" >=> staticFilesRequest
                        // ...
                        ] )

        POST >=>
            fun context ->
                context |> (
                    setCORSHeaders
                    >=> choose
                        [
                        path "/something" >=> runSomething
                        // ...
                        ] )
    ]

I'm sure there is a pretier way.

Rakes answered 23/8, 2017 at 11:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.