imalsogreg / servant-reflex

Generate reflex client functions for querying a servant API
BSD 3-Clause "New" or "Revised" License
79 stars 59 forks source link

Discussion: XSRF header for servant-auth clients #48

Open 3noch opened 7 years ago

3noch commented 7 years ago

servant-auth-server by default expects that the [browser] client will add an X-XSRF-TOKEN header with the contents of the XSRF-TOKEN cookie for authenticated requests. I have a PR servant-auth to make this configurable, but regardless it would be nice if servant-reflex made this easier. Now that we have #47, we can actually do this manually. In fact, that may be the only way currently. The reason is that servant-auth doesn't "advertise" in the types the name of the XSRF cookie or the header it expects. The server and the client need to negotiate that out-of-band (so to speak).

Thoughts?

3noch commented 6 years ago

BTW this extra header can be added by using clientWithOpts.

finlay commented 6 years ago

Hi

We made this work using the Web.Cookie (cookie) library to parse the cookie text. The following example is based on a Servant authentication API derived from servant-auth-server:

type Authenticated = Auth '[Cookie] User
type SetCookies = Headers '[ Header "Set-Cookie" SetCookie
                           , Header "Set-Cookie" SetCookie
                           ] NoContent
type AuthApi
    =  "login" :> ReqBody '[JSON] LoginInfo :> PostNoContent '[JSON] SetCookies
  :<|> Authenticated :> "user" :> Get '[JSON] User

This is the Servant.Reflex code required to get the cookie, and put it into the request header:

    -- Get the XHR-COOKIE if set
    let getToken = lookup "XSRF-TOKEN" . parseCookiesText . T.encodeUtf8
    xsrfToken <- liftJSM $ getToken <$> (currentDocumentUnchecked >>= getCookie)

    -- API access functions
    let tweakRequest = ClientOptions $ \r -> do
          return $ r & xhrRequest_config 
                     . xhrRequestConfig_headers 
                     . at "X-XSRF-TOKEN" .~ xsrfToken
        pm = Proxy :: Proxy m
        pn = Proxy :: Proxy ()
        bp = constDyn (BasePath "/")
        login :<|> user  = clientWithOpts api pm pn bp tweakRequest
3noch commented 6 years ago

@finlay Thanks for the full example! Is it possible to put the getToken bit into tweakRequest itself so you can derive these functions once and reuse them again for different tokens?

finlay commented 6 years ago

I think the issue is that the currentDocumentUnchecked function needs the MonadJSM context, which IO doesn't have.

And the ClientOptions type wraps a function in IO.

We could change the ClientOptions type a little perhaps. @hamishmack was wondering about that.

F

3noch commented 6 years ago

There is a way to get the JSM context and regain it again but I can't recall how. I'm sure @hamishmack knows.

imalsogreg commented 6 years ago

@finlay @3noch changing the type of ClentOptions sounds promising.