GithubHelp home page GithubHelp logo

servant-auth's People

Contributors

3noch avatar adetokunbo avatar adithyaov avatar akhesacaro avatar alpmestan avatar bts avatar cdepillabout avatar defanor avatar domenkozar avatar erewok avatar hamishmack avatar harendra-kumar avatar jkachmar avatar jkarni avatar karshan avatar kirelagin avatar maksbotan avatar mattaudesse avatar mchaver avatar mschristiansen avatar odr avatar phadej avatar qnikst avatar rockbmb avatar soenkehahn avatar sopvop avatar sordina avatar stephenirl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

servant-auth's Issues

cookie authentication without csrf

I'd like to protect a route that returns an HTML page with cookie authentication.

Based on the example from the README, I'd want my routes to look something like this:

type Protected
   = "name" :> Get '[JSON] String
 :<|> "email" :> Get '[JSON] String
 :<|> Raw

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

type API = Unprotected :<|> (Auth '[Cookie] User :> Protected)

If I'm using Cookie authentiation, the problem with this is that the Raw routes in Protected will require that the X-XSRF-TOKEN header is set. If a user is accessing a page in their browser and clicks a link, I don't think that the X-XSRF-TOKEN will be set. (Maybe it would be possible to do this somehow with Javascript, but I'd rather not have to take that approach.)

It would be nice to have a separate authentication like CookieNoCSRF that doesn't check the X-XSRF-TOKEN header. Is there a way to do this currently? Or would it require an additional authentication method? Or maybe there is a better way?

Cookie Auth broken?

Hi!

curl -v --cookie "JWT-Cookie=eyJhbGciOiJIUzI1NiJ9.eyJkYXQiOnsiZW1haWwiOiJhbGlAZW1haWwuY29tIiwibmFtZSI6IkFsaSBCYWJhIn19.U-PyzI3wJgmJA-aiRl_vH6Ij9l0XN3AmmjP_NtNnolk" http://localhost:7249/name
*   Trying ::1...
* connect to ::1 port 7249 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 7249 (#0)
> GET /name HTTP/1.1
> Host: localhost:7249
> User-Agent: curl/7.43.0
> Accept: */*
> Cookie: JWT-Cookie=eyJhbGciOiJIUzI1NiJ9.eyJkYXQiOnsiZW1haWwiOiJhbGlAZW1haWwuY29tIiwibmFtZSI6IkFsaSBCYWJhIn19.U-PyzI3wJgmJA-aiRl_vH6Ij9l0XN3AmmjP_NtNnolk
> 
< HTTP/1.1 401 Unauthorized
< Transfer-Encoding: chunked
< Date: Sat, 11 Feb 2017 15:11:50 GMT
< Server: Warp/3.2.9
< 
* Connection #0 to host localhost left intact

Using the readme tutorial, after successfully logging in.
It works using the same file with the JWT option.

Any idea how to debug this further?

CSRF gets reset too often causing race condition in browser

When CSRF is enabled, servant-auth-server will set the cookie on every response. The following will happen in the browser with concurrent requests:

  1. Request A is performed by the browser with token=t1
  2. Request B is constructed in JavaScript with token=t1
  3. Response A is received by the browser, sets the cookie to token=t2
  4. Request B gets sent by the browser with t1 in the XSRF header and t2 in the Cookie header
  5. Request B gets rejected even though it is legitimate

This may be more or less of a problem depending on the JavaScript technology used. The context switching by GHCJS probably makes this even more likely than otherwise, but Angular may also suffer from this race condition.

Tests fail when using 0.2.1.0 release

Running 2 test suites...
Test suite doctest: RUNNING...
doctest: InvalidYaml (Just (YamlException "Yaml file not found: package.yaml"))
Test suite doctest: FAIL
Test suite logged to: dist/test/servant-auth-0.2.1.0-doctest.log
Test suite spec: RUNNING...
Test suite spec: PASS
Test suite logged to: dist/test/servant-auth-0.2.1.0-spec.log
1 of 2 test suites (1 of 2 test cases) passed.

We probably need to add package.yaml to extra-source-files section of Cabal.

Issue with switching to 0.12's hoistServer

I had previously worked out how to make my stack with with this library, however, I am now attempting to upgrade to servant 0.12 but have the following problem when trying to use hoistServer.

Given:

toHandler :: AppContext -> AppM a -> Handler a
toHandler ctx r =
  (runLogging . runExceptT . flip runReaderT ctx . runAppM) r >>= \case
    Left e  -> throwError e
    Right v -> return v

server :: CookieSettings -> JWTSettings -> AppContext -> Server Routes
server ccf jcf ctx =
  let p = Proxy :: Proxy Routes
      h = toHandler ctx
      r = routes ccf jcf
  in hoistServer p h r

I get:

    • No instance for (HasContextEntry '[] CookieSettings)
        arising from a use of ‘hoistServer’
    • In the expression: hoistServer p h r
      In the expression:
        let
          p = Proxy :: Proxy Routes
          h = toHandler ctx
          r = routes ccf jcf
        in hoistServer p h r
      In an equation for ‘server’:
          server ccf jcf ctx
            = let
                p = ...
                h = toHandler ctx
                ....
              in hoistServer p h r

What am I missing?

Err 405 when using /login (README example)

After building the README example and running it with Cookie, I'm running:

curl -v -XPOST -H 'Content-Type: application/json' --data '{"username":"...", "password":"..."}' localhost:7249/login

And getting:

*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 7249 (#0)
> POST /login HTTP/1.1
> Host: localhost:7249
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 40
> 
* upload completely sent off: 40 out of 40 bytes
< HTTP/1.1 405 Method Not Allowed
< Transfer-Encoding: chunked
< Date: Mon, 23 Jan 2017 14:01:43 GMT
< Server: Warp/3.2.8
< Content-Type: text/plain
< 
* Connection #0 to host localhost left intact
Only GET or HEAD is supported

But the example is using PostNoContent for "/login"

Using:

  • servant-auth-server-0.2.1.0
  • servant-auth-0.2.1.0
  • jose-0.5.0.0

Update to jose 6

Jose 6 is out and is now in LTS. Some changes will be necessary.

JWT Instances for servant-foreign

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)
        }

Authorization versus Authentication

This intentionally overlaps/duplicates #5, since that issue is relatively old and servant-auth, as well as Servant itself, has changed substantially since then.

Right now it's looking like servant-auth is going to become the blessed form of authentication handling in Servant, and one of the areas that's really lacking right now is how to handle route authorization through a custom combinator.

I don't really have a great solution in my mind, except that I'd like to have a combinator - or documented process of writing some combinator - that accepts the result of servant-auth's authentication and can then authorize access to a route based on the information there.

It'd be particularly useful if there was a documented method to use such a combinator inside a custom application transformer stack, as my main motivating example would be decoding the authentication token and using a DB connection in some ReaderT stack to check the permissions for that user's ID.

Missing exports for checking auth

These should be exported to be able to check auth in raw WAI Applications:

  • Servant.Auth.Server.Internal.JWT.jwtAuthCheck
  • Servant.Auth.Server.Internal.Types.AuthCheck(..)

Cookie support in servant-auth-swagger

I'd like to use cookie based auth with toSwagger but I get this error

    • No instance for (Data.Swagger.Internal.ParamSchema.ToParamSchema
                             SetCookie)
       arising from a use of ‘toSwagger’
    • In the first argument of ‘(&)’, namely ‘toSwagger api’
      In the first argument of ‘(&)’, namely
        ‘toSwagger api & info . title .~ "Gramm API"’
      In the first argument of ‘(&)’, namely
        ‘toSwagger api & info . title .~ "Gramm API"
         & info . version .~ "1.0"’

What is necessary for cookie auth support? If it's not too complicated I'd be glad to take care of it.

The "WWW-Authenticate" header

When Servant.Auth.Server.BasicAuth is used, Servant.Auth doesn't
send the WWW-Authenticate header by itself, leaving it to a user. I
see the wwwAuthenticatedErr function in
Servant.Auth.Server.Internal.BasicAuth, but haven't found it being
exported from non-internal modules, and it is confusing overall: is it
intended for use by a user?

AddSetCookies regression for function types

It seems that AddSetCookies currently (0.2.2.0) doesn't work for function types: if I have a handler like Int -> Handler Int behind Auth schemes session, I get an Overlapping instances error.

For the following program,

#!/usr/bin/env stack
-- stack --resolver lts-7.8 --install-ghc runghc --package servant-0.9.1.1 --package servant-server-0.9.1.1 --package http-api-data-0.3.5 --package servant-auth-0.2.1.0 --package servant-auth-server-0.2.2.0 --package jose-0.5.0.0

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE TypeOperators     #-}

import Network.Wai.Handler.Warp            (run)
import Servant
import Servant.Auth.Server
import Servant.Auth.Server.SetCookieOrphan ()

instance ToJWT ()
instance FromJWT ()

type Protected = "int" :> ReqBody '[JSON] Int :> Post '[JSON] Int
type Api auths = (Auth auths () :> Protected)

api = Proxy :: Proxy (Api '[JWT, Cookie])

intHandler :: Int -> Handler Int
intHandler = return

protected :: AuthResult () -> Server Protected
protected (Authenticated user) = intHandler
protected _                    = throwAll err401

server :: CookieSettings -> JWTSettings -> Server (Api auths)
server cs jwts = protected

main = do
  myKey <- generateKey
  let jwtCfg = defaultJWTSettings myKey
      cfg = defaultCookieSettings :. jwtCfg :. EmptyContext
  run 8000 $ serveWithContext api cfg (server defaultCookieSettings jwtCfg)

I get this compilation error:

asc-bug.hs:34:14: error:
    • Overlapping instances for Servant.Auth.Server.Internal.AddSetCookie.AddSetCookies
                                  ('Servant.Auth.Server.Internal.AddSetCookie.S
                                     ('Servant.Auth.Server.Internal.AddSetCookie.S
                                        'Servant.Auth.Server.Internal.AddSetCookie.Z))
                                  (Int -> Control.Monad.Trans.Except.ExceptT ServantErr IO Int)
                                  (Int
                                   -> Control.Monad.Trans.Except.ExceptT
                                        ServantErr
                                        IO
                                        (Headers
                                           '[Header "Set-Cookie" SetCookie,
                                             Header "Set-Cookie" SetCookie]
                                           Int))
        arising from a use of ‘serveWithContext’
      Matching instances:
        instance [overlap ok] Servant.Auth.Server.Internal.AddSetCookie.AddSetCookies
                                n oldb newb =>
                              Servant.Auth.Server.Internal.AddSetCookie.AddSetCookies
                                n (a -> oldb) (a -> newb)
          -- Defined in ‘Servant.Auth.Server.Internal.AddSetCookie’
        ...plus one instance involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the second argument of ‘($)’, namely
        ‘serveWithContext api cfg (server defaultCookieSettings jwtCfg)’
      In a stmt of a 'do' block:
        run 8000
        $ serveWithContext api cfg (server defaultCookieSettings jwtCfg)
      In the expression:
        do { myKey <- generateKey;
             let jwtCfg = defaultJWTSettings myKey
                 cfg = defaultCookieSettings :. jwtCfg :. EmptyContext;
             run 8000
             $ serveWithContext api cfg (server defaultCookieSettings jwtCfg) }

When I switch from servant-auth-server 0.2.2.0 back to servant-auth-server 0.2.1.0, the code compiles successfully.

Better JWT verification failure information and add "dat" documentation

First off, thanks for making this. I have this successfully implemented and it does exactly what I need. I know this library is new, but want to give some feedback and see if I can help out. I'm fairly new to Haskell+Servant so I apologize if any of these are not issues with more knowledge.

One issue I had was dealing with AuthResult failures while trying to get everything to flow. What was difficult is when I received the AuthResult I had no insight into why it failed. For a long time I thought it was JWT configuration issues until I started pulling code out of the lib to see what was really happening. This part in particular:

https://github.com/plow-technologies/servant-auth/blob/ffa4c29c86f4deca8e183f2fb426e660e4551d9a/servant-auth-server/src/Servant/Auth/Server/Internal/JWT.hs#L62-L66

Once I realized it was actually verifying correctly and it was just JWT decode issues on my type, it was easy to get it going.

Another issue was not realizing that it was looking in unregistered claims for the keyword "dat". I'm using Auth0 to generate tokens and did not realize was trying to decode from there specifically. I assumed it'd be the entire payload.

Once again, thanks for putting this out there. I'm definitely open to help address these if you can point me in the right direction!

Support servant-auth-0.3.*

I'm trying to build servant-auth-server via nixpkgs.

$ nix-build -A haskellPackages.servant-auth-server
...
cabal: Encountered missing dependencies:
jose ==0.5.*, servant-auth ==0.2.*

The build environment has jose-0.6.0.3 and servant-auth-0.3.0.0. I see there's already a PR for upgrading to jose-0.6.

It would be great if servant-auth-server could support servant-auth-0.3.*.

CSRF tokens should not be required for stateless HTTP methods

Hi there,

Thank you for this library!

I've noticed that CSRF tokens seem to be required for GET, HEAD, OPTIONS, and TRACE requests, but these methods should never change any state, so they are safe to be performed without a token.

See:

I'm submitting another issue shortly, and I am going to try to help out with these issues.

`ThrowAll` and `EmptyServer`

I can't seem to use throwAll with an API type containing an EmptyServer anywhere in it. I have a MonadError ServantErr m instance for my monad stack (below) and throwAll works for other types of routes.

Here's a minimal reproduction:

newtype AppT m a
    = AppT
    { runApp :: ReaderT Environment (ExceptT ServantErr m) a
    } deriving ( Functor, Applicative, Monad, MonadReader Environment,
                 MonadError ServantErr, MonadIO)

data ApiUser = ApiUser
  { UserId :: Int64
  } deriving (Eq, Show, Generic, ToJSON, FromJSON)

instance ToJWT ApiUser
instance FromJWT ApiUser

type MyAPI auths = Auth auths ApiUser :> EmptyAPI

myPrivateServer :: MonadIO m => AuthResult ApiUser -> ServerT EmptyAPI (AppT m)
myPrivateServer (Authenticated _user) = emptyServer
myPrivateServer _ = throwAll err401

This unfortunately gives me the following error:

Could not deduce (Control.Monad.Error.Class.MonadError
                          ServantErr (Tagged (AppT m)))
        arising from a use of ‘throwAll’
      from the context: MonadIO m
        bound by the type signature for:
                   myPrivateServer :: MonadIO m =>
                                      AuthResult ApiUser -> ServerT EmptyAPI (AppT m)

HasClient instances seem not to be there?

Perhaps I've done something silly, but with this test code:

fullAPI :: Proxy (API '[Cookie])
fullAPI = Proxy
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}
module FullAPISpec where

import           Test.Hspec
import Web.FullAPI
import           Servant.Auth.Server                  (generateKey)
import Control.Concurrent(forkIO, killThread)
import Control.Exception(bracket)
import           Network.Wai.Handler.Warp             (run)
import           Network.Wai.Middleware.RequestLogger (logStdoutDev)
import Servant.Common.BaseUrl(BaseUrl(..))
import Servant.Client(client)
import Servant.Server(serveWithContext)
import Servant.Auth.Client()

withServer p = bracket (startServer p) killServer

killServer = killThread . fst

startServer p = do
  (cfg,server) <- mkServer <$> generateKey
  handle <- forkIO $ run p $ logStdoutDev $ serveWithContext fullAPI cfg server
  return (handle, client fullAPI) --  (BaseUrl Http "localhost" p))

spec :: Spec
spec =
  around (withServer 8888) $
  describe "full interaction" $ do
    it "should do things" $ \(cfg,server) -> do
      pending

I get this:

          
/home/mark/startup/emails/thirstyrando/test/FullAPISpec.hs:25:19: error:
    • No instance for Servant.Auth.Client.Internal.JWTAuthNotEnabled
        arising from a use of ‘client’
    • In the expression: client fullAPI
      In the first argument of ‘return’, namely
        ‘(handle, client fullAPI)’
      In a stmt of a 'do' block: return (handle, client fullAPI)

I might be doing something silly - have you got an example of using servant-client with a protected API?

Orphans for SetCookie break derived clients

Since the ToHttpApiData instances for SetCookie are only available as orphans in servant-auth-server, clients that use the same API type will not be able to derive client functions because these instances will be missing. These instances need to be pulled out into a different library or included in servant-auth itself.

(Cookie) No instance for Servant.Foreign.Internal.HasForeign

JavaScript code generation fails with the following error message:

/Users/kseo/KodeBoxProjects/kodebox/kodebox-server/jsgen/Main.hs: 27, 15
• No instance for (servant-foreign-0.9.1.1:Servant.Foreign.Internal.HasForeign
                     NoTypes
                     Servant.API.ContentTypes.NoContent
                     (servant-auth-0.2.1.0:Servant.Auth.Auth '[Cookie] Session
                      Servant.API.Sub.:> Kodebox.Server.Api.Protected))
    arising from a use of ‘writeJSForAPI’
• In the expression:
    writeJSForAPI
      restApi vanillaJS (joinPath [outputDir, "Kodebox.js"])
  In an equation for ‘writeJSCode’:
      writeJSCode
        = writeJSForAPI
            restApi vanillaJS (joinPath [outputDir, "Kodebox.js"])

Is ToJWT/FromJWT a requirement for a custom IsAuth?

I'm writing a custom Auth strategy for decrypting a Rails session cookie. I've got the following POC code working (actual decryption code, yet to be plugged in). This problem is:

  • This code compiles
  • And, the handler code with AuthResult RailsCookie.UserId in it's signature compiles,
  • But, the actual serveWithContext fails with the following error:
   194  25 error           error:
     • No instance for (Auth.ToJWT UserId)
         arising from a use of ‘serveWithContext’
     • In the expression:
         serveWithContext api (cookieSettings :. EmptyContext) server
       In an equation for ‘serverApplication’:
           serverApplication
             = serveWithContext api (cookieSettings :. EmptyContext) server
       In the expression:
         let
           cookieSettings = RailsCookieSettings {cookieName = "myAuth"}
           handlers = (PaxManifestEp.server :<|> TestEp.server)
           server = enter (NT appmToServantM) handlers
           ....
         in (serverApplication req respHandler) (intero)

Do I need to define ToJWT and FromJWT instances even though I have nothing to do with JWT?

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Servant.Auth.Server.Internal.RailsCookie
  (
    RailsCookie
  , RailsCookieSettings(..)
  , UserId
  ) where

import Servant.Auth.Server
import Data.Aeson
import GHC.Generics
import Servant.Auth.Server.Internal.Class
import Servant.Auth.Server.Internal.Types
import Control.Monad.Reader
import Web.Cookie
import Network.Wai (requestHeaders)
import Data.ByteString
import Data.ByteString.Char8 as C8

data RailsCookie
data RailsCookieSettings = RailsCookieSettings
  {
    cookieName :: ByteString
  } deriving (Show, Eq)

newtype UserId = UserId Integer deriving (Show, Eq, Ord, Generic)

instance IsAuth RailsCookie UserId where
  type AuthArgs RailsCookie = '[RailsCookieSettings]
  runAuth _ _ = railsCookieCheck

railsCookieCheck :: RailsCookieSettings -> AuthCheck UserId
railsCookieCheck cookieSettings = do
  request <- ask -- AuthCheck is a Reader with Request as its environment
  encryptedCookie <- maybe mempty return (mEncryptedCookie request)
  return $ UserId $ read $ C8.unpack encryptedCookie
  where
    mEncryptedCookie request = lookup "Cookie" (requestHeaders request)
      >>= (return.parseCookies)
      >>= lookup (cookieName cookieSettings)

Support servant-server 0.10

The latest version of servant-auth-server at the time of writing has a >=0.9.1 && <0.10 constraint on servant-server. Would be nice if the latest version of servant-server (0.10) was supported.

functions for reading and writing keys

first off, kudos! it was surprisingly easy to add auth to my app with this.

Now that i want to push it into prod, i need some way to not recreate the key every time i restart the process. is there an approved way to do that? should i consider Jose.Crypto as part of the public api?

make X-XSRF-TOKEN header for GET requests optional based on settings

The cookie authentication check seems to require the X-XSRF-TOKEN header irrespective of the request type. Refer to this code.

As far as I understand it is not necessary for GET requests to check for CSRF protection as it is not a state modifying operation. However, one can argue that someone can use GET for state modification. The current scheme is very restrictive and cannot work for GET requests from browsers without triggering it from javascript, we should make the GET request protection optional so that we can disable it if we want to.

Compile error with GHC 7.8

Fwiw, the cabal file specifies base >= 4.7

Configuring component lib from servant-auth-client-0.2.1.0
Preprocessing library servant-auth-client-0.2.1.0...
[1 of 2] Compiling Servant.Auth.Client.Internal ( src/Servant/Auth/Client/Internal.hs, /tmp/matrix-worker/1480349576/dist-newstyle/build/x86_64-linux/ghc-7.8.4/servant-auth-client-0.2.1.0/build/Servant/Auth/Client/Internal.o )

src/Servant/Auth/Client/Internal.hs:46:1:
    No parameters for class ‘JWTAuthNotEnabled’
    (Use NullaryTypeClasses to allow no-parameter classes)
    In the class declaration for ‘JWTAuthNotEnabled’

also:

Configuring component lib from servant-auth-docs-0.2.1.0
Preprocessing library servant-auth-docs-0.2.1.0...
[1 of 1] Compiling Servant.Auth.Docs ( src/Servant/Auth/Docs.hs, /tmp/matrix-worker/1480348605/dist-newstyle/build/x86_64-linux/ghc-7.8.4/servant-auth-docs-0.2.1.0/build/Servant/Auth/Docs.o )

src/Servant/Auth/Docs.hs:61:41:
    Not in scope: ‘<$>’
    Perhaps you meant ‘<>’ (imported from Data.Monoid)

src/Servant/Auth/Docs.hs:63:41:
    Not in scope: ‘<$>’
    Perhaps you meant ‘<>’ (imported from Data.Monoid)

However, I've also noticed a more fundamental problem with servant-auth-server:

Configuring component exe:readme from servant-auth-server-0.2.1.0
Preprocessing executable 'readme' for servant-auth-server-0.2.1.0...
ghc: could not execute: markdown-unlit

The package appears to build-depends on markdown-unlit, however, build-depends only requests the library component of a package, and not its executables. It was a bug in previous versions of cabal, and future versions of cabal don't support this anymore. There will be a tool-depends: ... for specifying executable dependencies, but it's not available yet.

No way to get a CSRF token when using creds-based auth and cookies

CSRF cookies seem to be set in response to requests authenticated with a Bearer token, but not in response to those authenticated with a cookie the user logging in with credentials.

It seems like this functionality should be swapped:

  • Responses to Bearer-authenticated (API) requests should not set a CSRF cookie
  • Responses to Cookie-authenticated the user logging in with creds (browser) requests should set a CSRF cookie

See also #10

Make auth issue debugging easier - attach reason to Indefinite

Currently it is hard to debug without changing the code of the package. For example, I ran into a cookie authentication issue and it was failing because X-XSRF-TOKEN was not present. The cookieAuthCheck code just calls the fail method if loopup fails which is not easy to see in the code ultimately it returns just Indefinite for all these failure cases. It would be of great help if we can attach failure reason with the Indefinite constructor.

Export SameSite et all

Right now SameSite and its constructors are in Internal.ConfigTypes even though it represents a core part of the API.

Protecting Raw routes.

I have a protected api type that defines a Raw route for websocket handling, like so:

type PrivApi =
         "foo" :> ReqBody '[JSON] Bar :> PostCreated '[JSON] Bar
    :<|> "foo" :> "stream" :> Raw

type Api =
         "login" :> ReqBody '[JSON] Creds :> Post '[JSON] Token
    :<|> Auth '[JWT] Session :> PrivApi

mkPrivApi :: AuthResult Session -> Server PrivApi
mkPrivApi (Authenticated s) = ...
mkPrivApi _                          = throwAll err401

The problem here is that throwAll does not work in this case:

    • Couldn't match type ‘GHC.IO.Exception.IOException’
                     with ‘ServantErr’
        arising from a functional dependency between:
          constraint ‘MonadError ServantErr IO’
            arising from a use of ‘throwAll’
          instance ‘MonadError GHC.IO.Exception.IOException IO’
            at <no location info>
    • In the expression: throwAll err401
      In an equation for ‘mkPrivApi’: mkPrivApi _ = throwAll err401

I tried to circumvent this by pulling the Raw route out of my PrivApi type (so that I can use throwAll) and putting it as an individually protected route in Api. But this lead to following error:

    • No instance for (HasServer
                         (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookieApi Raw)
                         '[JWTSettings, CookieSettings])
        arising from a use of ‘serveWithContext’

Is there any way I can protect Raw routes like these?

servant-auth-server 0.2.2.0 seems to break PVP

I have a project that compiled fine with servant-auth-server-0.2.1.0, but after the update to 0.2.2.0 started failing to build with the following errors:

backend/src/srv/Main.hs:121:25: error:
    • No instance for (ToHttpApiData SetCookie)
        arising from a use of ‘serveWithContext’
[...]

backend/test/Main.hs:99:13: error:
    • No instance for (ToHttpApiData
                         cookie-0.4.2.1:Web.Cookie.SetCookie)
        arising from a use of ‘serveWithContext’
[...]

This can be fixed by adding a Servant.Auth.Server.SetCookieOrphan () import to import the required instances (before that I only needed to import Servant.Auth.Server).

I suggest blacklisting 0.2.2.0 by adding a base < 0 dependency and releasing a fixed version 0.2.3.0 that exports the necessary instances from the same modules 0.2.1.0 did, or just bumping the package version to 0.3.0.0.

Suggestions for API changes and the addition of an Authorize combinator

Hey, moving the discussion from haskell-servant/servant#799 here. I had a few ideas for changes to the servant-auth API with the goal of separating the concerns of authentication, authorization, and the handler. The notable changes being:

  • New Authorize combinator which ensures an authenticated user is authorized to access a route
    • The handler for an Authorized route does not deal with the authentication and/or authorization failures that can happen
  • Existing Auth combinator becomes Authenticate with some small changes
    • Handler no longer takes AuthResult user, instead the Handler is enhanced with maybeAuthUser :: Handler (Maybe User) which can be used if desired
  • The AuthorizeHandler (name tbd) typeclass which centralizes the logic for the authorization check (:: user -> AuthorizationState user), what to do for an authorized route when either: a) authentication has not been performed, b) the user is unauthorized.

A short example of a route/handler before and after:

Current API:

type API auths
  = (Auth auths User :> Protected)
  :<|> ...

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

protected :: AuthResult User -> Server Protected
protected (Authenticated user) =
  if username user == "admin"
  then return (name user) :<|> return (email user)
  else throwAll err401
protected _ = throwAll err401

Proposed API:

data IsAdmin

type API auths
    = (Authenticate auths User :> Authorize IsAdmin User :> Protected)
    :<|> ...

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

instance AuthorizeHandler (IsAdmin User) where
  whenAuthenticationRequired _ = redirect "login"
  whenUnauthorized _ = throwAll err401
  authorize _ user =
    if username user == "admin"
    then return (Authorized user)
    else return (Unauthorized "Not admin")

protected :: User -> Server Protected
protected user = return (name user) :<|> return (email user)

I have a doc where I have most of the details (I thought it would be easier to collect thoughts there than in an issue). Feel free to leave comments there or here, it's still and early draft and would appreciate any feedback :)

support encrypted sessions

I'm working on a so far relatively simple application that stores some sensitive data in an encrypted cookie using servant's experimental general authentication. I'd love to switch to servant-auth as Servant's new default, but am missing the functionality to encrypt session data.

One approach might be to encrypt session payloads for cookies with a HttpOnly flag by default. If the cookie is not meant to be inspected by client side code its content might as well be encrypted to hide it from malicious browser extensions and other potential attackers as well. Such an approach would not entail any loss of functionality as far as I can see, while eliminating a potential error on the side of the library user.

I'm looking forward on others' thoughts on this.

Question about login response

Looking to the example code in the readme, I wonder if it's possible to not respond with token in cookie but to return a JSON object in the repsonse body which contains the token?

When I change:

 let jwtCfg = defaultJWTSettings myKey
      cfg = defaultCookieSettings :. jwtCfg :. EmptyContext
      --- Here we actually make concrete
      api = Proxy :: Proxy (API '[JWT])

to

 let jwtCfg = defaultJWTSettings myKey
      cfg =  jwtCfg :. EmptyContext
      --- Here we actually make concrete
      api = Proxy :: Proxy (API '[JWT])

I get this error:
No instance for (HasContextEntry '[] CookieSettings) arising from a use of ‘serveWithContext’

Syntax highlighting in README

I was going to submit a PR to turn on syntax highlighting for the examples in the readme, but it looks like the examples are already marked up for highlighting, just not using github flavoured markdown. Briefly looking online I couldn't find anywhere that this readme was rendered other than here (hackage points users here for viewing the readme).

Is this a throwback to when this readme would be displayed elsewhere, and it can just be updated to use GFM now?

support writing custom combinators for authorization

The given Auth combinator takes care of authentication. It would be nice if servant-auth (or servant) allowed to easily write a custom combinator MyAuth that used servant-auth-server for authentication, but then also took care of authorization, where

Server (MyAuth :> api) ~ MyUser -> Server api

The HasServer instance would yield 401s for missing or invalid authentication and 403 for insufficient privileges.

Prepare to be official

servant-auth is becoming the official auth mechanism for servant. Before that can happen, we should

  • Update the tutorial (in the servant repo)
  • Fix #52 / #59
  • Maybe also #8 ? (That's a little less clear to me; it's not like the current servant authentication combinators have useful instances there)
  • Add option without cookies

ThrowAll + ServerT

Given an existing stack I've been hacking on without servant-auth:

newtype AppM a
  = AppM { runAppM :: ReaderT AppContext (LoggingT (WithSeverity Doc) IO) a }
  deriving ( Functor, Applicative, Monad
           , MonadIO, MonadThrow
           , MonadReader AppContext, MonadLog (WithSeverity Doc))

And some sub-set of my API where I try to include servant-auth:

type ClientApiProtected =
       AssocTerm :> AssociationApi
  :<|> AuthTerm :> AuthApi
  :<|> ManagedAuthTerm :> ManagedAuthApi
  :<|> PermissionTerm :> PermissionApi
  :<|> ServiceTerm :> ServiceApi

type ClientApiUnprotected = UserTerm :> UserApi

type ClientApi =
       (Auth '[JWT] Session :> ClientApiProtected)
  :<|> ClientApiUnprotected


clientApiProtectedServer
  :: AuthResult Session -> ServerT ClientApiProtected AppM
clientApiProtectedServer (Authenticated _) =
       associationApiServer
  :<|> authApiServer
  :<|> managedAuthApiServer
  :<|> permissionApiServer
  :<|> serviceApiServer
clientApiProtectedServer _ = throwAll err401 -- Gah!

clientApiUnprotectedServer
  :: CookieSettings -> JWTSettings -> ServerT ClientApiUnprotected AppM
clientApiUnprotectedServer cs jwts = userApiServer

clientApiServer :: CookieSettings -> JWTSettings -> ServerT ClientApi AppM
clientApiServer cs jwts =
       clientApiProtectedServer
  :<|> clientApiUnprotectedServer cs jwts

I end up with the error:

    • No instance for (Control.Monad.Error.Class.MonadError
                         ServantErr AppM)
        arising from a use of ‘throwAll’
    • In the expression: throwAll err401
      In an equation for ‘clientApiProtectedServer’:
          clientApiProtectedServer _ = throwAll err401

What am I missing here? Attempting to add MonadError ServantErr instances to my AppM leads me down other rabbit holes, although maybe I am simply doing it wrong.

Getting started example

Hi, I am new to servant and, implicitly, servant-auth. I managed to get the code running in my local instance. However, I am still not clear on how to use the code to protect resources. Would it be possible to provide a more complete example - having user creating accounts or token + defining protected/unprotected endpoints? I do see in the current example code calling

localhost:7249/name -v

However, I still do not see where that is defined, i.e. Thanks for your help.

No instance of `ToHttpApiData` for `ByteString`

I get

src/Servant/Auth/Server/Internal/AddSetCookie.hs:40:27: error:
    • Could not deduce (ToHttpApiData BS.ByteString)
        arising from a use of ‘addSetCookie’
      from the context: Functor m
        bound by the instance declaration
        at src/Servant/Auth/Server/Internal/AddSetCookie.hs:(38,3)-(39,78)
    • In the first argument of ‘(<$>)’, namely ‘addSetCookie cookie’
      In the expression: addSetCookie cookie <$> v
      In an equation for ‘addSetCookie’:
          addSetCookie cookie v = addSetCookie cookie <$> v
cabal: Leaving directory '/tmp/cabal-tmp-23420/servant-auth-server-0.2.0.0'
cabal: Error: some packages failed to install:
servant-auth-server-0.2.0.0-GXwN6DuRKCC9tAHlBkHFuA failed during the building
phase. The exception was:
ExitFailure 1

Looking at the definition of ToHttpApiData, I am unable to find an instance for ByteString (not in version 0.3.3 either).

I have compiled against both versions 0.3.2 and 0.3.3, resulting in the same error.

Finally,

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.0.1.20160919

Contents of CSRF tokens not being checked?

Is it intentional that the contents of the CSRF token seem to not be checked for any cryptographic properties, but only that the two values (in the header, and in the cookie) match? maybe per https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers?

This request succeeds for any token value:

curl -v -H "X-XSRF-TOKEN: foo" -H "Cookie: session=session_goes_here; XSRF-TOKEN=foo" -H "Accept: application/vnd.api+json" "http://localhost:3000/check"

If this intentional and if this suffices to protect against CSRF, maybe we shouldn't bother with #27 or even CSRF tokens at all? and instead just require browsers to set the header X-Requested-With: XMLHttpRequest as suggested in the owasp link above.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.