packages feed

jwt 0.2.1 → 0.3.0

raw patch · 4 files changed

+72/−24 lines, 4 filesdep −tasty-hspec

Dependencies removed: tasty-hspec

Files

changelog view
@@ -1,3 +1,9 @@+2014-06-02 0.3.0+================++	* Add verify function (thanks to Robert Massaioli) to allow verifying an+	already decoded JWT token+ 2014-03-10 0.2.1 ================= 
jwt.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                jwt-version:             0.2.1+version:             0.3.0 synopsis:            JSON Web Token (JWT) decoding and encoding license:             MIT license-file:        LICENSE@@ -62,7 +62,6 @@   build-depends:       base < 5 && >= 4.4                      , tasty >= 0.7                      , tasty-th >= 0.1-                     , tasty-hspec >= 0.1                      , tasty-hunit >= 0.4                      , tasty-quickcheck >= 0.3                      , HUnit
src/Web/JWT.hs view
@@ -28,7 +28,9 @@     (     -- * Encoding & Decoding JWTs     -- ** Decoding+    -- $docDecoding       decode+    , verify     , decodeAndVerifySignature     -- ** Encoding     , encodeSigned@@ -104,24 +106,24 @@  -- | The JSON Web Token data JWT r where-   Unverified :: JWTHeader -> JWTClaimsSet -> JWT UnverifiedJWT+   Unverified :: JWTHeader -> JWTClaimsSet -> Signature -> T.Text -> JWT UnverifiedJWT    Verified   :: JWTHeader -> JWTClaimsSet -> Signature -> JWT VerifiedJWT  deriving instance Show (JWT r)  -- | Extract the claims set from a JSON Web Token claims :: JWT r -> JWTClaimsSet-claims (Unverified _ c) = c+claims (Unverified _ c _ _) = c claims (Verified _ c _) = c  -- | Extract the header from a JSON Web Token header :: JWT r -> JWTHeader-header (Unverified h _) = h+header (Unverified h _ _ _) = h header (Verified h _ _) = h  -- | Extract the signature from a verified JSON Web Token signature :: JWT r -> Maybe Signature-signature (Unverified _ _) = Nothing+signature Unverified{}     = Nothing signature (Verified _ _ s) = Just s  -- | A JSON numeric value representing the number of seconds from@@ -271,14 +273,40 @@ -- Just (JWTClaimsSet {iss = Nothing, sub = Nothing, aud = Nothing, exp = Nothing, nbf = Nothing, iat = Nothing, jti = Nothing, unregisteredClaims = fromList [("some",String "payload")]}) decode :: JSON -> Maybe (JWT UnverifiedJWT) decode input = do-    (h,c) <- extractElems $ T.splitOn "." input+    (h,c,s) <- extractElems $ T.splitOn "." input     let header' = parseJWT h         claims' = parseJWT c-    Unverified <$> header' <*> claims'+    Unverified <$> header' <*> claims' <*> (pure . Signature $ s) <*> (pure . dotted $ [h,c])     where-        extractElems (h:c:_) = Just (h,c)+        extractElems (h:c:s:_) = Just (h,c,s)         extractElems _       = Nothing +-- | Using a known secret and a decoded claims set verify that the signature is correct+-- and return a verified JWT token as a result.+--+-- This will return a VerifiedJWT if and only if the signature can be verified using the+-- given secret.+--+-- The separation between decode and verify is very useful if you are communicating with+-- multiple different services with different secrets and it allows you to lookup the+-- correct secret for the unverified JWT before trying to verify it. If this is not an+-- isuse for you (there will only ever be one secret) then you should just use+-- 'decodeAndVerifySignature'.+--+-- >>> :{+--  let+--      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text+--      mUnverifiedJwt = decode input+--      mVerifiedJwt = verify (secret "secret") =<< mUnverifiedJwt+--  in signature =<< mVerifiedJwt+-- :}+-- Just (Signature "Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U")+verify :: Secret -> JWT UnverifiedJWT -> Maybe (JWT VerifiedJWT)+verify secret' (Unverified header' claims' unverifiedSignature originalClaim) = do+   algo <- alg header'+   let calculatedSignature = Signature $ calculateDigest algo secret' originalClaim+   guard (unverifiedSignature == calculatedSignature)+   pure $ Verified header' claims' calculatedSignature  -- | Decode a claims set and verify that the signature matches by using the supplied secret. -- The algorithm is based on the supplied header value.@@ -290,22 +318,11 @@ --  let --      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text --      mJwt = decodeAndVerifySignature (secret "secret") input---  in join $ fmap signature mJwt+--  in signature =<< mJwt -- :} -- Just (Signature "Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U")-decodeAndVerifySignature :: Secret -> T.Text -> Maybe (JWT VerifiedJWT)-decodeAndVerifySignature secret' input = do-        (h,c,s) <- extractElems $ T.splitOn "." input-        header' <- parseJWT h-        claims' <- parseJWT c-        algo  <- fmap alg header'-        let sign = if Just s == calculateMessageDigest h c algo then pure $ Signature s else mzero-        Verified <$> header' <*> claims' <*> sign-    where-      calculateMessageDigest header' claims' (Just algo') = Just $ calculateDigest algo' secret' (dotted [header', claims'])-      calculateMessageDigest _ _ Nothing = Nothing-      extractElems (h:c:s:_) = Just (h,c,s)-      extractElems _         = Nothing+decodeAndVerifySignature :: Secret -> JSON -> Maybe (JWT VerifiedJWT)+decodeAndVerifySignature secret' input = verify secret' =<< decode input  -- | Try to extract the value for the issue claim field 'iss' from the web token in JSON form tokenIssuer :: JSON -> Maybe StringOrURI@@ -424,4 +441,24 @@     parseJSON (String s) = return $ S s     parseJSON _          = mzero -+-- $docDecoding+-- There are three use cases supported by the set of decoding/verification+-- functions:+--+-- (1) Plaintext JWTs (<http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-16#section-6>).+--      This is supported by the decode function 'decode'.+--      As a client you don't care about signing or encrypting so you only get back a 'JWT' 'UnverifiedJWT'.+--      I.e. the type makes it clear that no signature verification was attempted.+--+-- (2) Signed JWTs you want to verify using a known secret.+--      This is what 'decodeAndVerifySignature' supports, given a secret+--      and JSON it will return a 'JWT' 'VerifiedJWT' if the signature can be+--      verified.+--+-- (3) Signed JWTs that need to be verified using a secret that depends on+--      information contained in the JWT. E.g. the secret depends on+--      some claim, therefore the JWT needs to be decoded first and after+--      retrieving the appropriate secret value, verified in a subsequent step.+--      This is supported by using the `verify` function which given+--      a 'JWT' 'UnverifiedJWT' and a secret will return a 'JWT' 'VerifiedJWT' iff the+--      signature can be verified.
tests/src/Web/JWTTests.hs view
@@ -51,6 +51,12 @@     Just HS256 @=? alg (header unverified)     Just "payload" @=? Map.lookup "some" (unregisteredClaims $ claims unverified) +case_verify = do+    -- Generated with ruby-jwt+    let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U"+        mVerified = verify (secret "secret") =<< decode input+    True @=? isJust mVerified+ case_decodeAndVerifyJWT = do     -- Generated with ruby-jwt     let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U"