Search code examples
haskellservant

How to modify the default SetCookie configuration in servant-auth


After successful logging in, servant-auth sets a JWT-Cookie cookie in the response. The problem is, it also flags the cookie as HttpOnly, which means I can't read the JWT in my single page app. My understanding is that this can be configured with the SetCookie datatype. I've been able to create my own custom SetCookie by using defaultSetCookie { setCookieHttpOnly = False }. How do I stuff it into the acceptLogin function?

Here is my handler for logging in.

checkCreds :: CookieSettings
           -> JWTSettings
           -> LoginRequest
           -> App (Headers '[ Header "Set-Cookie" SetCookie
                            , Header "Set-Cookie" SetCookie ]
                            NoContent)
checkCreds cookieSettings jwtSettings LoginRequest{email = email, rawPassword = rawPassword} = do
   maybeUser <- logicForFetchingAndValidatingUser
   -- What do I do with this mySetCookie? 
   let mySetCookie = defaultSetCookie { setCookieHttpOnly = False }
   case maybeUser of
     Just userView -> do
      mApplyCookies <- liftIO $ acceptLogin cookieSettings jwtSettings userView
      case mApplyCookies of
        Nothing           -> throwError err401
        Just applyCookies -> return $ applyCookies NoContent
     Nothing -> throwError err401

Solution

  • I've read the source of acceptLogin and figured out it calls makeSessionCookie and makeXsrfCookie, then uses the results to create a function for adding headers. I did the same in my code, modyfing the result of makeSessionCookie along the way:

    checkCreds :: CookieSettings
               -> JWTSettings
               -> LoginRequest
               -> App (Headers '[ Header "Set-Cookie" SetCookie
                                , Header "Set-Cookie" SetCookie]
                                NoContent)
    checkCreds cookieSettings jwtSettings LoginRequest{email = email, rawPassword = rawPassword} = do
       -- Usually you would ask a database for the user info. This is just a
       -- regular servant handler, so you can follow your normal database access
       -- patterns (including using 'enter').
       maybeUser <- logicForFetchingAndValidatingUser
       case maybeUser of
         Just userView -> do
          mSessionCookie <- liftIO $ makeSessionCookie cookieSettings jwtSettings userView
          case mSessionCookie of
            Nothing -> throwError err401
            Just sessionCookie -> do
              let modifiedSessionCookie = sessionCookie { setCookieHttpOnly = False, setCookieSecure = False }
              xsrfCookie <- liftIO $ makeXsrfCookie cookieSettings
              return $ (addHeader modifiedSessionCookie . addHeader xsrfCookie) NoContent
    
         Nothing -> throwError err401