mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
server: configuration to specify which Header to pick for JWT Authentication
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9942 GitOrigin-RevId: 4f18d29f07c213a001d43b88e3ce2991230e28e6
This commit is contained in:
parent
c12c8e974c
commit
7647456718
@ -542,6 +542,7 @@ The following are the possible values:
|
||||
|
||||
- `{"type": "Authorization"}`
|
||||
- `{"type": "Cookie", "name": "cookie_name" }`
|
||||
- `{"type": "CustomHeader", "name": "header_name" }`
|
||||
|
||||
Default is `{"type": "Authorization"}`.
|
||||
|
||||
@ -550,6 +551,8 @@ In the default mode, Hasura expects an `Authorization` header with a `Bearer` to
|
||||
In the cookie mode, Hasura will try to parse the cookie header with the given cookie name. The value of the cookie
|
||||
should be the exact JWT.
|
||||
|
||||
In the custom header mode, Hasura expects a `header_name` header with the exact JWT token value.
|
||||
|
||||
Example:
|
||||
|
||||
If `header` is `{"type": "Authorization"}` then JWT header should look like:
|
||||
@ -564,6 +567,12 @@ If `header` is `{"type": "Cookie", "name": "cookie_name" }` then JWT header shou
|
||||
Cookie: cookie_name=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...
|
||||
```
|
||||
|
||||
If `header` is `{"type": "CustomHeader", "name": "header_name" }` then JWT header should look like:
|
||||
|
||||
```none
|
||||
header_name: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...
|
||||
```
|
||||
|
||||
## Run Hasura GraphQL Engine in JWT mode {#running-with-jwt}
|
||||
|
||||
### Docker
|
||||
|
@ -138,6 +138,7 @@ instance J.ToJSON JWTClaimsFormat where
|
||||
data JWTHeader
|
||||
= JHAuthorization
|
||||
| JHCookie Text -- cookie name
|
||||
| JHCustomHeader Text -- header name
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
instance Hashable JWTHeader
|
||||
@ -148,7 +149,8 @@ instance J.FromJSON JWTHeader where
|
||||
if
|
||||
| hdrType == "Authorization" -> pure JHAuthorization
|
||||
| hdrType == "Cookie" -> JHCookie <$> o J..: "name"
|
||||
| otherwise -> fail "expected 'type' is 'Authorization' or 'Cookie'"
|
||||
| hdrType == "CustomHeader" -> JHCustomHeader <$> o J..: "name"
|
||||
| otherwise -> fail "expected 'type' is 'Authorization' or 'Cookie' or 'CustomHeader'"
|
||||
|
||||
instance J.ToJSON JWTHeader where
|
||||
toJSON JHAuthorization = J.object ["type" J..= ("Authorization" :: String)]
|
||||
@ -157,6 +159,11 @@ instance J.ToJSON JWTHeader where
|
||||
[ "type" J..= ("Cookie" :: String),
|
||||
"name" J..= name
|
||||
]
|
||||
toJSON (JHCustomHeader name) =
|
||||
J.object
|
||||
[ "type" J..= ("CustomHeader" :: String),
|
||||
"name" J..= name
|
||||
]
|
||||
|
||||
defaultClaimsFormat :: JWTClaimsFormat
|
||||
defaultClaimsFormat = JCFJson
|
||||
@ -537,6 +544,7 @@ processJwt_ processJwtBytes decodeIssuer fGetHeaderType jwtCtxs headers mUnAuthR
|
||||
case BC.words b' of
|
||||
["Bearer", jwt] -> pure jwt
|
||||
_ -> throw400 InvalidHeaders "Malformed Authorization header"
|
||||
(JHCustomHeader _, b') -> pure b'
|
||||
|
||||
case (StringOrURI <$> jcxIssuer j, decodeIssuer $ RawJWT $ BLC.fromStrict b'') of
|
||||
(Nothing, _) -> pure $ Right (j, b'')
|
||||
@ -551,13 +559,22 @@ processJwt_ processJwtBytes decodeIssuer fGetHeaderType jwtCtxs headers mUnAuthR
|
||||
keyCtxOnAuthTypes :: [JWTCtx] -> HashMap.HashMap AuthTokenLocation [JWTCtx]
|
||||
keyCtxOnAuthTypes = HashMap.fromListWith (++) . fmap (expectedHeader &&& pure)
|
||||
|
||||
keyTokensOnAuthTypes :: [HTTP.Header] -> HashMap.HashMap AuthTokenLocation [(AuthTokenLocation, B.ByteString)]
|
||||
keyTokensOnAuthTypes = HashMap.fromListWith (++) . map (fst &&& pure) . concatMap findTokensInHeader
|
||||
getCustomHeaderName :: JWTHeader -> Maybe Text
|
||||
getCustomHeaderName = \case
|
||||
JHCustomHeader headerName -> Just headerName
|
||||
_ -> Nothing
|
||||
|
||||
findTokensInHeader :: Header -> [(AuthTokenLocation, B.ByteString)]
|
||||
findTokensInHeader (key, val)
|
||||
getCustomHeaderNameList = mapMaybe (getCustomHeaderName . fGetHeaderType) jwtCtxs
|
||||
|
||||
keyTokensOnAuthTypes :: [HTTP.Header] -> HashMap.HashMap AuthTokenLocation [(AuthTokenLocation, B.ByteString)]
|
||||
keyTokensOnAuthTypes = HashMap.fromListWith (++) . map (fst &&& pure) . concatMap (findTokensInHeader getCustomHeaderNameList)
|
||||
|
||||
findTokensInHeader :: [Text] -> Header -> [(AuthTokenLocation, B.ByteString)]
|
||||
findTokensInHeader jwtCustomHeaderList (key, val)
|
||||
| key == CI.mk "Authorization" = [(JHAuthorization, val)]
|
||||
| key == CI.mk "Cookie" = bimap JHCookie T.encodeUtf8 <$> Spock.parseCookies val
|
||||
| key `elem` (map (CI.mk . T.encodeUtf8) jwtCustomHeaderList) =
|
||||
[(JHCustomHeader (T.toLower $ T.decodeUtf8 $ CI.original key), val)]
|
||||
| otherwise = []
|
||||
|
||||
expectedHeader :: JWTCtx -> AuthTokenLocation
|
||||
@ -565,6 +582,7 @@ processJwt_ processJwtBytes decodeIssuer fGetHeaderType jwtCtxs headers mUnAuthR
|
||||
case fGetHeaderType jwtCtx of
|
||||
JHAuthorization -> JHAuthorization
|
||||
JHCookie name -> JHCookie name
|
||||
JHCustomHeader name -> JHCustomHeader (T.toLower name)
|
||||
|
||||
withAuthZ authzHeader jwtCtx = do
|
||||
authMode <- processJwtBytes jwtCtx $ BL.fromStrict authzHeader
|
||||
|
@ -54,6 +54,8 @@ def get_header_fmt(conf):
|
||||
return (hdr_fmt, None)
|
||||
elif hdr_fmt == 'Cookie':
|
||||
return (hdr_fmt, conf['header']['name'])
|
||||
elif hdr_fmt == "CustomHeader":
|
||||
return (hdr_fmt, conf['header']['name'])
|
||||
else:
|
||||
raise Exception('Invalid JWT header format: %s' % conf)
|
||||
except KeyError:
|
||||
@ -68,6 +70,8 @@ def mk_authz_header(conf, token):
|
||||
return {'Authorization': 'Bearer ' + token}
|
||||
elif header == 'Cookie' and name:
|
||||
return {'Cookie': name + '=' + token}
|
||||
elif header == 'CustomHeader' and name:
|
||||
return {name: token}
|
||||
else:
|
||||
raise Exception('Invalid JWT header format')
|
||||
|
||||
@ -375,6 +379,12 @@ class TestJwtBasicWithEd25519AndCookie(AbstractTestJwtBasic):
|
||||
class TestJwtBasicWithEsAndCookie(AbstractTestJwtBasic):
|
||||
pass
|
||||
|
||||
@pytest.mark.jwt('rsa', {
|
||||
'header': {'type': 'CustomHeader', 'name': 'hasura_user'},
|
||||
})
|
||||
class TestJwtBasicWithRsaAndCustomHeader(AbstractTestJwtBasic):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.jwt('rsa', {
|
||||
'claims_format': 'stringified_json',
|
||||
|
Loading…
Reference in New Issue
Block a user