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

JWT Instances for servant-foreign

Open expipiplus1 opened this issue 9 years ago • 9 comments

It would be handy to have instances for HasForeign from servant-foreign

Something along the lines of this, I'm not sure about the correctness of using Text to represent JWTs, but I believe this is along the right lines. It could probably be generalised a bit too so it's not fixed to exactly '[JWT]

instance forall lang ftype api.
    ( HasForeign lang ftype api
    , HasForeignType lang ftype Text
    )
  => HasForeign lang ftype (Auth '[JWT] a :> api) where
  type Foreign ftype (Auth '[JWT] a :> api) = Foreign ftype api

  foreignFor lang Proxy Proxy subR =
    foreignFor lang Proxy (Proxy :: Proxy api) req
    where
      req = subR{ _reqHeaders = HeaderArg arg : _reqHeaders subR }
      arg = Arg
        { _argName = PathSegment "Authorization"
        , _argType = typeFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy Text)
        }

expipiplus1 avatar Oct 27 '16 00:10 expipiplus1

Looks good. I'll start a new package for it.

My inclination is to have the instance be lang ftyp (Auth (JWT ': etc) a :> api), so that for most cases, the first authentication mechanism is picked, except for JS (or purescript etc.) where we require that Cookie be available somewhere in the list. The idea is that then you could derive authentication with sane defaults both for browser and "normal" languages.

This kind of screws over Node, but I'm okay punting on that problem.

jkarni avatar Mar 01 '17 22:03 jkarni

@jkarni What is the package? :)

sordina avatar May 03 '17 12:05 sordina

I've had another go at this, with some success. Note that this requires GHC > 8.0 (but it will likely be possible to support earlier versions with the same principle). I'm not sure how it interacts with haskell-servant/servant-auth#31 or haskell-servant/servant#706. I've tested with purescript-bridge and purescript-servant-support (though I haven't tested the generated code itself, yet).

It would be good to see some progress, as part of the integration of servant-auth.


My inclination is to have the instance be lang ftyp (Auth (JWT ': etc) a :> api), so that for most cases, the first authentication mechanism is picked, except for JS (or purescript etc.) where we require that Cookie be available somewhere in the list. The idea is that then you could derive authentication with sane defaults both for browser and "normal" languages.

This approach prefers Cookie auth over straight up JWT; I'm not sure how one would go about handling both.

The closed type family generates the header name, and in principle another would be used to automatically ensure that the Bearer: prefix is added in the case of JWT mode.

type family TokenHeaderName xs :: Symbol where
  TokenHeaderName (Cookie ': xs) = "X-XSRF-TOKEN"
  TokenHeaderName (JWT ': xs) = "Authorization"
  TokenHeaderName (x ': xs) = TokenHeaderName xs
  TokenHeaderName '[] = TypeError (Text "Neither JWT nor cookie auth enabled")
instance
    ( TokenHeaderName auths ~ header
    , KnownSymbol header
    , HasForeignType lang ftype Token
    , HasForeign lang ftype sub
    )
    => HasForeign lang ftype (Auth auths a :> sub) where
  type Foreign ftype (Auth auths a :> sub) = Foreign ftype sub

  foreignFor lang Proxy Proxy req =
    foreignFor lang Proxy subP $ req & reqHeaders <>~ [HeaderArg arg]
    where
      arg   = Arg
        { _argName = PathSegment . T.pack $ symbolVal @header Proxy
        , _argType = token
        }
      token = typeFor lang (Proxy @ftype) (Proxy @Token)
      subP  = Proxy @sub

Annoyingly the TypeError construct seems to require UndecidableInstances (this is a longstanding GHC bug) but it straightforwardly terminates.

(Edit: typo with TokenHeaderName in instance constraint)

dbaynard avatar Jan 13 '18 20:01 dbaynard

I'd like to suggest to keep the token header type family open, so as to support other auth schemes, which is the end goal of servant-auth. Similarly, should we always assume auth schemes use a header?

Regardless, thanks already! We should indeed do our best to get this into a mergeable shape :)

alpmestan avatar Jan 13 '18 23:01 alpmestan

This is surely not the only place in the library where that might be assumed. I've no knowledge of any other schemes, so sadly I can't answer on that. I suspect the Symbol approach may work rather well, as whichever form of authentication is used, the identifiers must be representable as text.

Anyway, keep it up. And I'll contribute any useful advances, here.

dbaynard avatar Jan 13 '18 23:01 dbaynard

@dbaynard oh it may not be indeed! I didn't mean to criticize your work. Certainly not. But in the context of the Google Summer of Code / Haskell.org Summer of Code, we might put together a proposal to implement a whole bunch of common auth schemes (see this ticket), so anything that makes that a little bit easier is welcome =)

alpmestan avatar Jan 14 '18 12:01 alpmestan

Relevant: https://github.com/mattjbray/servant-elm/issues/11

domenkozar avatar Oct 02 '18 11:10 domenkozar

Oh and there is a WIP branch from @jkarni in 2017: https://github.com/haskell-servant/servant-auth/commit/7bef1f994a429b053ea507b054cf25637f5c1dcb

domenkozar avatar Nov 13 '18 17:11 domenkozar

Oh and there is a WIP branch from @jkarni in 2017: 7bef1f9

I've found this while trying to use servant-elm with servant-auth - for now i use the orphan instances from the commit above to be able to test servant-elm. Is there a better solution as of today?

ikervagyok avatar Apr 11 '21 20:04 ikervagyok