add custom namespace in jwt claims (close #350) (#364)

This commit is contained in:
Anon Ray 2018-09-07 06:00:50 +00:00 committed by Shahidh K Muhammed
parent fd53eb9c1e
commit f726bb549d
2 changed files with 36 additions and 16 deletions

View File

@ -54,8 +54,9 @@ $(A.deriveJSON (A.aesonDrop 3 A.snakeCase) ''HasuraClaims)
-- | HGE's own representation of various JWKs -- | HGE's own representation of various JWKs
data JWTConfig data JWTConfig
= JWTConfig = JWTConfig
{ jcType :: !T.Text { jcType :: !T.Text
, jcKey :: !JWK , jcKey :: !JWK
, jcClaimNs :: !(Maybe T.Text)
} deriving (Show, Eq) } deriving (Show, Eq)
allowedRolesClaim :: T.Text allowedRolesClaim :: T.Text
@ -64,6 +65,9 @@ allowedRolesClaim = "x-hasura-allowed-roles"
defaultRoleClaim :: T.Text defaultRoleClaim :: T.Text
defaultRoleClaim = "x-hasura-default-role" defaultRoleClaim = "x-hasura-default-role"
defaultClaimNs :: T.Text
defaultClaimNs = "https://hasura.io/jwt/claims"
-- | Process the request headers to verify the JWT and extract UserInfo from it -- | Process the request headers to verify the JWT and extract UserInfo from it
processJwt processJwt
:: ( MonadIO m :: ( MonadIO m
@ -78,9 +82,16 @@ processJwt conf headers = do
-- verify the JWT -- verify the JWT
claims <- liftJWTError invalidJWTError $ verifyJwt (jcKey conf) jwt claims <- liftJWTError invalidJWTError $ verifyJwt (jcKey conf) jwt
let claimsNs = fromMaybe defaultClaimNs $ jcClaimNs conf
-- see if the hasura claims key exist in the claims map
let mHasuraClaims = Map.lookup claimsNs $ claims ^. unregisteredClaims
hasuraClaimsV <- maybe claimsNotFound return mHasuraClaims
-- the value of hasura claims key has to be an object
hasuraClaims <- validateIsObject hasuraClaimsV
-- filter only x-hasura claims -- filter only x-hasura claims
let claimsMap = Map.filterWithKey (\k _ -> T.isPrefixOf "x-hasura-" k) $ let claimsMap = Map.filterWithKey (\k _ -> T.isPrefixOf "x-hasura-" k) hasuraClaims
claims ^. unregisteredClaims
HasuraClaims allowedRoles defaultRole <- parseHasuraClaims claimsMap HasuraClaims allowedRoles defaultRole <- parseHasuraClaims claimsMap
let role = getCurrentRole defaultRole let role = getCurrentRole defaultRole
@ -107,6 +118,11 @@ processJwt conf headers = do
["Bearer", jwt] -> return jwt ["Bearer", jwt] -> return jwt
_ -> malformedAuthzHeader _ -> malformedAuthzHeader
validateIsObject jVal =
case jVal of
A.Object x -> return x
_ -> throw400 JWTInvalidClaims "hasura claims should be an object"
-- see if there is a x-hasura-role header, or else pick the default role -- see if there is a x-hasura-role header, or else pick the default role
getCurrentRole defaultRole = getCurrentRole defaultRole =
let userRoleHeaderB = TE.encodeUtf8 userRoleHeader let userRoleHeaderB = TE.encodeUtf8 userRoleHeader
@ -131,6 +147,9 @@ processJwt conf headers = do
throw400 InvalidHeaders "Missing Authorization header in JWT authentication mode" throw400 InvalidHeaders "Missing Authorization header in JWT authentication mode"
currRoleNotAllowed = currRoleNotAllowed =
throw400 AccessDenied "Your current role is not in allowed roles" throw400 AccessDenied "Your current role is not in allowed roles"
claimsNotFound = do
let claimsNs = fromMaybe defaultClaimNs $ jcClaimNs conf
throw400 JWTInvalidClaims $ "claims key: '" <> claimsNs <> "' not found"
-- parse x-hasura-allowed-roles, x-hasura-default-role from JWT claims -- parse x-hasura-allowed-roles, x-hasura-default-role from JWT claims
@ -191,26 +210,27 @@ instance A.FromJSON JWTConfig where
parseJSON = A.withObject "JWTConfig" $ \o -> do parseJSON = A.withObject "JWTConfig" $ \o -> do
keyType <- o A..: "type" keyType <- o A..: "type"
rawKey <- o A..: "key" rawKey <- o A..: "key"
claimNs <- o A..:? "claims_namespace"
case keyType of case keyType of
"HS256" -> parseHmacKey rawKey 256 keyType "HS256" -> parseHmacKey rawKey 256 keyType claimNs
"HS384" -> parseHmacKey rawKey 384 keyType "HS384" -> parseHmacKey rawKey 384 keyType claimNs
"HS512" -> parseHmacKey rawKey 512 keyType "HS512" -> parseHmacKey rawKey 512 keyType claimNs
"RS256" -> parseRsaKey rawKey keyType "RS256" -> parseRsaKey rawKey keyType claimNs
"RS384" -> parseRsaKey rawKey keyType "RS384" -> parseRsaKey rawKey keyType claimNs
"RS512" -> parseRsaKey rawKey keyType "RS512" -> parseRsaKey rawKey keyType claimNs
-- TODO: support ES256, ES384, ES512, PS256, PS384 -- TODO: support ES256, ES384, ES512, PS256, PS384
_ -> invalidJwk ("Key type: " <> T.unpack keyType <> " is not supported") _ -> invalidJwk ("Key type: " <> T.unpack keyType <> " is not supported")
where where
parseHmacKey key size ktype = do parseHmacKey key size ktype cns = do
let secret = BL.fromStrict $ TE.encodeUtf8 key let secret = BL.fromStrict $ TE.encodeUtf8 key
when (BL.length secret < size `div` 8) $ when (BL.length secret < size `div` 8) $
invalidJwk "Key size too small" invalidJwk "Key size too small"
return $ JWTConfig ktype (fromOctets secret) return $ JWTConfig ktype (fromOctets secret) cns
parseRsaKey key ktype = do parseRsaKey key ktype cns = do
let res = fromRawPem (BL.fromStrict $ TE.encodeUtf8 key) let res = fromRawPem (BL.fromStrict $ TE.encodeUtf8 key)
err e = "Could not decode PEM: " <> T.unpack e err e = "Could not decode PEM: " <> T.unpack e
either (invalidJwk . err) (return . JWTConfig ktype) res either (invalidJwk . err) (\r -> return $ JWTConfig ktype r cns) res
invalidJwk msg = fail ("Invalid JWK: " <> msg) invalidJwk msg = fail ("Invalid JWK: " <> msg)

View File

@ -181,8 +181,8 @@ parseJwtSecret =
jwtSecretHelp :: String jwtSecretHelp :: String
jwtSecretHelp = "The JSON containing type and the JWK used for verifying. e.g: " jwtSecretHelp = "The JSON containing type and the JWK used for verifying. e.g: "
<> "`{\"type\": \"HS256\", \"key\": \"<your-hmac-shared-secret>\"}`," <> "`{\"type\": \"HS256\", \"key\": \"<your-hmac-shared-secret>\", \"claims_namespace\": \"<optional-custom-claims-key-name>\"}`,"
<> "`{\"type\": \"RS256\", \"key\": \"<your-PEM-RSA-public-key>\"}`" <> "`{\"type\": \"RS256\", \"key\": \"<your-PEM-RSA-public-key>\", \"claims_namespace\": \"<optional-custom-claims-key-name>\"}`"
parseCorsConfig :: Parser CorsConfigFlags parseCorsConfig :: Parser CorsConfigFlags