Haskell Yesod - CORS problems with browsers OPTIONS requests when doing POST requests

1.1k Views Asked by At

I have used Network.Wai.Middleware.Cors's simpleCors, it worked properly for GET requests, but when I try to make a POST request I get the following problem

OPTIONS /users
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Status: 400 Bad Request 0.032443s

The only way I was able to make it work was by removing the simpleCors from the following part in Application.hs

-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
-- applying some additional middlewares.
makeApplication :: App -> IO Application
makeApplication foundation = do
    logWare <- makeLogWare foundation
    -- Create the WAI application and apply middlewares
    appPlain <- toWaiAppPlain foundation
    return $ logWare $ defaultMiddlewaresNoLogging $ simpleCors $ appPlain

and adding a OPTIONS method response

optionsNewUserR :: Handler RepPlain
optionsNewUserR = do
  return $ RepPlain $ toContent ("" :: Text)

and adding CORS headers... But it is a dirty solution, because I would need to change ALL my API handlers! Any help is highly appreciated!

2

There are 2 best solutions below

3
On BEST ANSWER

I believe the issue is that simpleCors is built off simpleCorsResourcePolicy, which only covers simpleMethods, which doesn't cover OPTIONS.

You can fix this issue by using the same methods to roll whatever middleware you need.

Here's the one I use for the OPTIONS problem you've described:

{-# LANGUAGE OverloadedStrings #-}

module Middlewares where

import Network.Wai                       (Middleware)
import Network.Wai.Middleware.AddHeaders (addHeaders)
import Network.Wai.Middleware.Cors       (CorsResourcePolicy(..), cors)

-- | @x-csrf-token@ allowance.
-- The following header will be set: @Access-Control-Allow-Headers: x-csrf-token@.
allowCsrf :: Middleware
allowCsrf = addHeaders [("Access-Control-Allow-Headers", "x-csrf-token,authorization")]

-- | CORS middleware configured with 'appCorsResourcePolicy'.
corsified :: Middleware
corsified = cors (const $ Just appCorsResourcePolicy)

-- | Cors resource policy to be used with 'corsified' middleware.
--
-- This policy will set the following:
--
-- * RequestHeaders: @Content-Type@
-- * MethodsAllowed: @OPTIONS, GET, PUT, POST@
appCorsResourcePolicy :: CorsResourcePolicy
appCorsResourcePolicy = CorsResourcePolicy {
    corsOrigins        = Nothing
  , corsMethods        = ["OPTIONS", "GET", "PUT", "POST"]
  , corsRequestHeaders = ["Authorization", "Content-Type"]
  , corsExposedHeaders = Nothing
  , corsMaxAge         = Nothing
  , corsVaryOrigin     = False
  , corsRequireOrigin  = False
  , corsIgnoreFailures = False
}

And then just compose the middlewares you need like you're already doing:

run port $ logger . allowCsrf . corsified $ app cfg
0
On

Simplifying the answer of Mario we can use simplePolicy

import Network.Wai.Middleware.Cors 

allowCors :: Middleware
allowCors = cors (const $ Just appCorsResourcePolicy)

appCorsResourcePolicy :: CorsResourcePolicy
appCorsResourcePolicy =
    simpleCorsResourcePolicy
        { corsMethods = ["OPTIONS", "GET", "PUT", "POST"]
        , corsRequestHeaders = ["Authorization", "Content-Type"]
        }

And to use it:

main = scotty 8080 $ do
    middleware allowCors
    matchAny  "/" $ text "Success"