servant-auth icon indicating copy to clipboard operation
servant-auth copied to clipboard

Missing part in readme: Can't successfully use cookie from browser

Open bratfizyk opened this issue 4 years ago • 2 comments

I've been trying to implement a very simple server with even simpler Lucid client that would work with Cookie auth server presented in this project's Readme. However, based on the information found there I'm unable to implement this. The problem is that I always get "AuthResult Indefinite" instead of "Authenticated" even though I get the cookie and my browser manages to process it. I guess I must be missing a tiny part, but not sure which one.

I've got a simple api, as suggested in the tutorial:

data User = User String
    deriving (Eq, Show, Generic)

data Credentials = Credentials {
    credentialsUserName :: String,
    credentialsPassword :: String
} deriving (Eq, Show, Read, Generic)

type Unprotected =
    "logMe" :> Get '[HTML] (Html ())
    :<|> "login" 
            :> ReqBody '[FormUrlEncoded] Credentials 
            :> Verb 'POST 204 '[JSON] (Headers '[ Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] NoContent)

type Protected
   = "name" :> Get '[JSON] String

type AuthAPI =
    (Servant.Auth.Server.Auth '[Cookie] User :> Protected)
    :<|> Unprotected

I added the "logMe" endpoint that contains a simple form that works with "Credentials" type. This part of code most likely works ok, as I get a cookie in my browser. image

Then, when I try poking the "name" endpoint, I get "Indefinite" result. I guess my project is missing a part that does cookie to User conversion, but I'm not sure how to add this. Any hints ?

Other relevant pieces of my code (very similar to the content of Readme):

cookieConfig :: CookieSettings
cookieConfig = defaultCookieSettings { cookieIsSecure = NotSecure }

getJwtConfig :: IO JWTSettings
getJwtConfig = do
    key <- generateKey 
    return $ defaultJWTSettings key

context :: CookieSettings -> JWTSettings -> (Context '[CookieSettings, JWTSettings])
context cookieConfig jwtConfig = cookieConfig :. jwtConfig :. EmptyContext

protected :: Servant.Auth.Server.AuthResult User -> Server Protected
protected (Servant.Auth.Server.Authenticated (User name)) = return name
protected x = trace ("Access Denied " ++ (show x)) $ throwAll err401

checkCreds :: CookieSettings -> JWTSettings  -> Credentials 
                                -> Handler (Headers '[ Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] NoContent)
checkCreds cookieSettings jwtSettings (Credentials { credentialsUserName = "Ali Baba", credentialsPassword = "Open Sesame"}) = do
    mApplyCookies <- liftIO $ acceptLogin cookieSettings jwtSettings (User "Ali Baba")
    case mApplyCookies of
        Nothing           -> trace "Nothing" $ throwError err401
        Just applyCookies -> return $ applyCookies NoContent
        
checkCreds _ _ (Credentials { credentialsUserName = user, credentialsPassword = _}) = 
    trace ("Received " ++ user)
        throwError err401

main :: IO ()
main = do
    migrateDB
    jwtConfig <- getJwtConfig
    putStrLn $ "Serving endpoint " ++ (show port)
    run port $ serveWithContext proxy (context cookieConfig jwtConfig) (appAPI cookieConfig jwtConfig)

Dislaimer: I'm quite new to Haskell, so it might be the case that I'm missing an important concept here.

bratfizyk avatar Jul 14 '20 21:07 bratfizyk

It's since XSRF also checks GET requests (terrible default). I recommend disabling XSRF and setting cookieSameSite setting to SameSiteStrict

domenkozar avatar Jul 15 '20 13:07 domenkozar

I tried

cookieConfig :: CookieSettings
cookieConfig = defaultCookieSettings { cookieIsSecure = NotSecure, cookieSameSite = SameSiteStrict, cookieXsrfSetting = Nothing }

and it did the job, thanks. Over the weekend I'll think about a few other features (e.g. how to delete a cookie to log user out) and will come up with a PR for the README file so that this use case becomes more obvious.

bratfizyk avatar Jul 15 '20 22:07 bratfizyk