Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support servant-auth using JWT in cookies #36

Merged
merged 1 commit into from
Aug 8, 2017
Merged

Conversation

3noch
Copy link
Collaborator

@3noch 3noch commented Aug 7, 2017

I'd like to also add some documentation on how to actually set up auth using servant-auth, but that's actually a bit more involved so I'll do it another PR.

@3noch
Copy link
Collaborator Author

3noch commented Aug 7, 2017

Re: documentation. The tricky part about servant-auth is that it uses very specific types for the API spec. From the example:

type API =
 "login"
     :> ReqBody '[JSON] Login
     :> PostNoContent '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
                                        , Header "Set-Cookie" SetCookie]
                                       NoContent)

The problem with this is that SetCookie (from cookie) needs some [orphan] instances that are supplied by servant-auth-server in order to work as servant header types. Clearly the client won't have access to these instances because the server library supplies them.

We have some options:

  1. Copy these orphans into our client-only code base and let the server and client share these instances separately. This seems particularly ugly.
  2. Figure out a way to share these instances between both server and client (likely by a change to servant-auth-server). But where would you put these? You could use a newtype over SetCookie to help?
  3. Parameterize your API spec on this type and specify SetCookie on the server and some nominal type (e.g. Text) on the client side. A slight variation of this would be to use CPP.

For option 3, the code could be:

type API setCookie =
 "login"
     :> ReqBody '[JSON] Login
     :> PostNoContent '[JSON] (Headers '[ Header "Set-Cookie" setCookie
                                        , Header "Set-Cookie" setCookie]
                                       NoContent)

-- Server
type ServerAPI = API SetCookie

-- Client
type ClientAPI = API Text

One advantage to option 3 is that the client code doesn't need to add a needless dependency on cookie. This really is a needless dependency because the client won't be benefiting from these cookies (or the types that capture them) in any way. In this case, that's actually the point as these must be HTTP-only cookies as well, which means the client can't use them. And since the SetCookie type is opaque (it says nothing about authentication in particular), there is really no benefit to keeping this information on the client.

A more aggressive option would be to parameterize the API type on the entire Header ... portion of the login endpoint.

I am looking for feedback before I decide to write up docs for this.

@3noch
Copy link
Collaborator Author

3noch commented Aug 7, 2017

Oh, I should also mention that to practically use servant-auth in a Reflex-DOM app, some changes are needed to support alternative CSRF options in servant-auth-server. I've started these here: haskell-servant/servant-auth#54

@imalsogreg
Copy link
Owner

This is great, thanks for all your research!

I like options 2 and 3. Maybe a 4th option would be to send a PR to http-api-data and to standardize the ToHttpApiData and FromHttpApiData instances there? cookie's dependencies are very light, I don't see a problem compiling it with ghcjs.

@imalsogreg imalsogreg merged commit cab484b into master Aug 8, 2017
@3noch 3noch deleted the servant-auth branch August 8, 2017 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants