@@ -47,7 +47,8 @@ import Data.Text (Text)
4747import Data.Text qualified as Text
4848import Data.Text.Lazy qualified as TL
4949import Data.Text.Lazy.Encoding qualified as TL
50- import Data.Time (UTCTime )
50+ import Data.Time (UTCTime , getCurrentTime )
51+ import Data.Time qualified as Time
5152import Network.HTTP.Client qualified as HTTP
5253import Network.HTTP.Client.TLS qualified as HTTPTLS
5354import Network.URI qualified as URI
@@ -146,6 +147,8 @@ data JWTSettings = JWTSettings
146147 -- | Locations to fetch external JWKs from.
147148 -- Typically this will be a map of issuer: "<issuer>/.well-known/jwks.json"
148149 externalJWKLocations :: Map Issuer URI ,
150+ -- | When we last checked each issuer for external JWKs.
151+ lastCheckedVar :: TVar (Map Issuer UTCTime ),
149152 -- | The set of audiences the app accepts tokens for.
150153 acceptedAudiences :: Set Audience ,
151154 -- | The set of issuers the app accepts tokens from.
@@ -244,15 +247,17 @@ data KeyMap = KeyMap
244247-- | This instance is used to look up the verification keys for a given JWT, assuring that the
245248-- expected algorithm and key id matches.
246249instance (MonadIO m , HasClaimsSet payload ) => JWT. VerificationKeyStore m (JWT. JWSHeader () ) payload JWTSettings where
247- getVerificationKeys header claims (JWTSettings {validationKeys = KeyMap {keysVar, legacyKey}, externalJWKLocations}) = do
250+ getVerificationKeys header claims (JWTSettings {validationKeys = KeyMap {keysVar, legacyKey}, externalJWKLocations, lastCheckedVar }) = do
248251 -- Issuer is required on normal claims, but old hashJWTs may not have one.
249252 let mayIssuer = claims ^? JWT. claimIss . _Just . JWT. uri . to Issuer
250253 case mayIssuer of
251254 Nothing -> pure $ maybeToList legacyKey
252255 Just jwtIssuer -> do
253256 matchingJWKs <- getJwksForIssuer jwtIssuer
254257 case matchingJWKs of
255- [] -> refreshExternalJWKs jwtIssuer
258+ [] -> do
259+ tryRefreshExternalJWKs jwtIssuer
260+ getJwksForIssuer jwtIssuer
256261 matchingJWKs -> pure matchingJWKs
257262 where
258263 getJwksForIssuer :: Issuer -> m [JWT. JWK ]
@@ -276,9 +281,17 @@ instance (MonadIO m, HasClaimsSet payload) => JWT.VerificationKeyStore m (JWT.JW
276281 pure jwk
277282 _ -> empty
278283 _ -> pure mempty
279- refreshExternalJWKs :: (MonadIO m ) => Issuer -> m [ JWT. JWK ]
280- refreshExternalJWKs iss = fmap (fromMaybe [] ) . runMaybeT $ do
284+ tryRefreshExternalJWKs :: (MonadIO m ) => Issuer -> m ()
285+ tryRefreshExternalJWKs iss = void . runMaybeT $ do
281286 uri <- hoistMaybe $ Map. lookup iss externalJWKLocations
287+ lastChecked <- readTVarIO lastCheckedVar
288+ now <- liftIO $ getCurrentTime
289+ -- Recheck at most every minute
290+ case (Map. lookup iss lastChecked) of
291+ Nothing -> pure ()
292+ Just lastCheckedTime ->
293+ guard $ Time. diffUTCTime now lastCheckedTime > (60 :: Time. NominalDiffTime )
294+
282295 -- We only fetch external JWKs from manually configured trusted jwk URIs
283296 -- so it's safe not to use a proxy.
284297 httpMan <- liftIO $ HTTPTLS. getGlobalManager
0 commit comments