Refine the jwt api

This commit is contained in:
Arian van Putten 2015-10-18 22:35:26 +02:00 committed by aaron levin
parent 2b3df72fa2
commit dc699a93e0
8 changed files with 29 additions and 23 deletions

View file

@ -11,7 +11,7 @@ import Data.ByteString.Base64 (encode)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding (decodeUtf8) import Data.Text.Encoding (decodeUtf8)
import Servant.API.Authentication (BasicAuth(BasicAuth)) import Servant.API.Authentication (BasicAuth(BasicAuth), JWTAuth(..))
import Servant.Common.Req (addHeader, Req) import Servant.Common.Req (addHeader, Req)
-- | Class to represent the ability to authenticate a 'Request' -- | Class to represent the ability to authenticate a 'Request'
@ -25,8 +25,7 @@ instance AuthenticateRequest (BasicAuth realm) where
let authText = decodeUtf8 ("Basic " <> encode (user <> ":" <> pass)) in let authText = decodeUtf8 ("Basic " <> encode (user <> ":" <> pass)) in
addHeader "Authorization" authText req addHeader "Authorization" authText req
type JSON = Text instance AuthenticateRequest JWTAuth where
instance AuthenticateRequest JSON where authReq (JWTAuth token) req =
authReq token req =
let authText = ("Bearer " <> token) let authText = ("Bearer " <> token)
in addHeader "Authorization" authText req in addHeader "Authorization" authText req

View file

@ -10,6 +10,8 @@ import Data.List
import GHC.Generics import GHC.Generics
import Network.Wai import Network.Wai
import Servant import Servant
import Servant.Server.Internal.Authentication
data Position = Position data Position = Position
{ x :: Int { x :: Int

View file

@ -31,7 +31,8 @@ library
servant-server >= 0.4, servant-server >= 0.4,
transformers >= 0.3 && <0.5, transformers >= 0.3 && <0.5,
QuickCheck >= 2.7 && <2.9, QuickCheck >= 2.7 && <2.9,
wai >= 3.0 && <3.1 wai >= 3.0 && <3.1,
jwt
hs-source-dirs: src hs-source-dirs: src
default-language: Haskell2010 default-language: Haskell2010

View file

@ -64,7 +64,7 @@ import Network.Wai
import Servant import Servant
import Servant.API.Authentication import Servant.API.Authentication
import Servant.API.ContentTypes import Servant.API.ContentTypes
import Servant.Server.Internal.Authentication import Web.JWT
import Test.QuickCheck.Arbitrary (Arbitrary (..), vector) import Test.QuickCheck.Arbitrary (Arbitrary (..), vector)
import Test.QuickCheck.Gen (Gen, generate) import Test.QuickCheck.Gen (Gen, generate)
@ -156,9 +156,8 @@ instance (HasMock rest, Arbitrary usr, KnownSymbol realm)
instance (HasMock rest, Arbitrary usr) instance (HasMock rest, Arbitrary usr)
=> HasMock (AuthProtect JWTAuth (usr :: *) 'Strict :> rest) where => HasMock (AuthProtect JWTAuth (usr :: *) 'Strict :> rest) where
mock _ = strictProtect (\_ -> do { a <- generate arbitrary; return (Just a)}) mock _ = strictProtect (\_ -> do { a <- generate arbitrary; return (Just a)})
(AuthHandlers (return authFailure) ((const . return) authFailure)) jwtAuthHandlers
(\_ -> mock (Proxy :: Proxy rest)) (\_ -> mock (Proxy :: Proxy rest))
where authFailure = responseBuilder status401 [] mempty
instance (Arbitrary a, AllCTRender ctypes a) => HasMock (Delete ctypes a) where instance (Arbitrary a, AllCTRender ctypes a) => HasMock (Delete ctypes a) where
mock _ = mockArbitrary mock _ = mockArbitrary

View file

@ -71,12 +71,14 @@ module Servant.Server
, err504 , err504
, err505 , err505
, module Servant.Server.Internal.Authentication
) where ) where
import Data.Proxy (Proxy) import Data.Proxy (Proxy)
import Network.Wai (Application) import Network.Wai (Application)
import Servant.Server.Internal import Servant.Server.Internal
import Servant.Server.Internal.Enter import Servant.Server.Internal.Enter
import Servant.Server.Internal.Authentication
-- * Implementing Servers -- * Implementing Servers

View file

@ -14,7 +14,7 @@ module Servant.Server.Internal.Authentication
, laxProtect , laxProtect
, strictProtect , strictProtect
, jwtAuthHandlers , jwtAuthHandlers
, jwtAuth , jwtAuthStrict
) where ) where
import Control.Monad (guard, (<=<)) import Control.Monad (guard, (<=<))
@ -37,10 +37,10 @@ import Network.Wai (Request, Response, requestHeaders,
import Servant.API.Authentication (AuthPolicy (Strict, Lax), import Servant.API.Authentication (AuthPolicy (Strict, Lax),
AuthProtected, AuthProtected,
BasicAuth (BasicAuth), BasicAuth (BasicAuth),
JWTAuth) JWTAuth (..))
import Web.JWT (JWT, UnverifiedJWT, VerifiedJWT, Secret, JSON) import Web.JWT (JWT, UnverifiedJWT, VerifiedJWT, Secret, JSON)
import qualified Web.JWT as JWT (decode, verify, secret) import qualified Web.JWT as JWT (decode, decodeAndVerifySignature, secret)
-- | Class to represent the ability to extract authentication-related -- | Class to represent the ability to extract authentication-related
-- data from a 'Request' object. -- data from a 'Request' object.
@ -122,21 +122,23 @@ instance AuthData JWTAuth where
-- We might want to write a proper parser for this? but split works fine... -- We might want to write a proper parser for this? but split works fine...
hdr <- lookup "Authorization" . requestHeaders $ req hdr <- lookup "Authorization" . requestHeaders $ req
["Bearer", token] <- return . splitOn " " . decodeUtf8 $ hdr ["Bearer", token] <- return . splitOn " " . decodeUtf8 $ hdr
_ <- JWT.decode token -- try decode it. otherwise it's not a proper token JWT.decode token -- try decode it. otherwise it's not a proper token
return token return . JWTAuth $ token
jwtAuthHandlers :: AuthHandlers JSON jwtAuthHandlers :: AuthHandlers JWTAuth
jwtAuthHandlers = jwtAuthHandlers = AuthHandlers (return missingData) ((const . return) authFailure)
let authFailure = responseBuilder status401 [] mempty where
in AuthHandlers (return authFailure) ((const . return) authFailure) withError e =
responseBuilder status401 [("WWW-Authenticate", "Bearer error=\""<>e<>"\"")] mempty
missingData = withError "invalid_request"
authFailure = withError "invalid_token"
-- | A default implementation of an AuthProtected for JWT. -- | A default implementation of an AuthProtected for JWT.
-- Use this to quickly add jwt authentication to your project. -- Use this to quickly add jwt authentication to your project.
-- One can use strictProtect and laxProtect to make more complex authentication -- One can use strictProtect and laxProtect to make more complex authentication
-- and authorization schemes. For an example of that, see our tutorial: @placeholder@ -- and authorization schemes.
-- TODO more advanced one jwtAuthStrict :: Secret -> subserver -> AuthProtected JWTAuth (JWT VerifiedJWT) subserver 'Strict
jwtAuth :: Text -> subserver -> AuthProtected JSON (JWT VerifiedJWT) subserver 'Strict jwtAuthStrict secret subserver = strictProtect (return . JWT.decodeAndVerifySignature secret . unJWTAuth) jwtAuthHandlers subserver
jwtAuth secret subserver = strictProtect (return . (JWT.verify (JWT.secret secret) <=< JWT.decode)) jwtAuthHandlers subserver

View file

@ -9,7 +9,7 @@ module Servant.API.Authentication
, AuthProtect (..) , AuthProtect (..)
, AuthProtected (..) , AuthProtected (..)
, BasicAuth (..) , BasicAuth (..)
, JWTAuth , JWTAuth (..)
) where ) where
@ -38,4 +38,4 @@ data BasicAuth (realm :: Symbol) = BasicAuth { baUser :: ByteString
, baPass :: ByteString , baPass :: ByteString
} deriving (Eq, Show, Typeable) } deriving (Eq, Show, Typeable)
type JWTAuth = Text newtype JWTAuth = JWTAuth { unJWTAuth :: Text }

View file

@ -16,4 +16,5 @@ packages:
extra-deps: extra-deps:
- engine-io-wai-1.0.2 - engine-io-wai-1.0.2
- control-monad-omega-0.3.1 - control-monad-omega-0.3.1
- jwt-0.6.0
resolver: nightly-2015-10-08 resolver: nightly-2015-10-08