Skip to content

Commit bfe61f0

Browse files
claudecmackenzie1
authored andcommitted
Add tracing diagnostics for JWT decode failures
Replaces silent internal-server errors with structured tracing output so operators can diagnose decode failures without reimplementing decoding logic themselves. - local: downgrade per-key failures from error to debug (expected when iterating multiple keys) and switch to key/value field syntax - remote: add info on initialize start/completion with jwks_url; warn on empty cache, unknown kid, and missing kid; debug on header parse and validation failures; warn per JWKS fetch attempt with attempt number; warn on JWK parse failure; convert existing error log to key/value style Token content and claims are never logged; kid values and error kinds are the only context emitted. https://claude.ai/code/session_013A5MSE1JJcmHjt6KjAiVoT
1 parent 06efdf1 commit bfe61f0

2 files changed

Lines changed: 30 additions & 15 deletions

File tree

src/local.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ where
121121
match jsonwebtoken::decode::<T>(token, key, &self.validation) {
122122
Ok(token_data) => return Ok(token_data),
123123
Err(e) => {
124-
tracing::error!("Error decoding token: {}", e);
124+
tracing::debug!(error = %e, "failed to decode token with key");
125125
last_error = Some(Error::Jwt(e));
126126
}
127127
}

src/remote.rs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,9 @@ impl RemoteJwksDecoder {
156156
/// shutdown_token.cancel();
157157
/// ```
158158
pub async fn initialize(&self) -> Result<CancellationToken, Error> {
159-
// Fetch keys immediately
159+
tracing::info!(jwks_url = %self.jwks_url, "initializing JWKS decoder");
160160
self.refresh_keys().await?;
161+
tracing::info!("JWKS decoder initialized, starting background refresh task");
161162

162163
// Create cancellation token for graceful shutdown
163164
let shutdown_token = CancellationToken::new();
@@ -199,8 +200,14 @@ impl RemoteJwksDecoder {
199200
match self.refresh_keys_once().await {
200201
Ok(_) => return Ok(()),
201202
Err(e) => {
202-
err = Some(e);
203203
attempt += 1;
204+
tracing::warn!(
205+
attempt,
206+
max_attempts,
207+
error = %e,
208+
"JWKS fetch attempt failed"
209+
);
210+
err = Some(e);
204211
tokio::time::sleep(self.config.backoff).await;
205212
}
206213
}
@@ -229,7 +236,10 @@ impl RemoteJwksDecoder {
229236
let mut new_keys = Vec::new();
230237
for jwk in jwks.keys.iter() {
231238
let key_id = jwk.common.key_id.to_owned();
232-
let key = DecodingKey::from_jwk(jwk).map_err(Error::Jwt)?;
239+
let key = DecodingKey::from_jwk(jwk).map_err(|e| {
240+
tracing::warn!(kid = ?key_id, error = %e, "failed to parse JWK");
241+
Error::Jwt(e)
242+
})?;
233243
new_keys.push((key_id.unwrap_or_default(), key));
234244
}
235245

@@ -284,15 +294,14 @@ impl RemoteJwksDecoder {
284294
break;
285295
}
286296
_ = tokio::time::sleep(self.config.cache_duration) => {
287-
tracing::info!("Refreshing JWKS");
297+
tracing::debug!("refreshing JWKS keys");
288298
match self.refresh_keys().await {
289299
Ok(_) => {}
290300
Err(err) => {
291-
// log the error and continue with stale keys
292301
tracing::error!(
293-
"Failed to refresh JWKS after {} attempts: {:?}",
294-
self.config.retry_count,
295-
err
302+
error = %err,
303+
retry_count = self.config.retry_count,
304+
"failed to refresh JWKS, continuing with stale keys"
296305
);
297306
}
298307
}
@@ -309,6 +318,7 @@ impl RemoteJwksDecoder {
309318
/// that `initialize()` was never called.
310319
fn check_initialized(&self) -> Result<(), Error> {
311320
if self.keys_cache.is_empty() {
321+
tracing::warn!("JWKS key cache is empty; initialize() may not have been called");
312322
Err(Error::Configuration(
313323
"JWKS decoder not initialized: call initialize() after building the decoder".into(),
314324
))
@@ -419,20 +429,25 @@ where
419429
{
420430
Box::pin(async move {
421431
self.check_initialized()?;
422-
let header = jsonwebtoken::decode_header(token)?;
432+
433+
let header = jsonwebtoken::decode_header(token).map_err(|e| {
434+
tracing::debug!(error = %e, "failed to decode JWT header");
435+
Error::Jwt(e)
436+
})?;
423437
let target_kid = header.kid;
424438

425439
if let Some(ref kid) = target_kid {
426440
if let Some(key) = self.keys_cache.get(kid) {
427-
Ok(jsonwebtoken::decode::<T>(
428-
token,
429-
key.value(),
430-
&self.validation,
431-
)?)
441+
jsonwebtoken::decode::<T>(token, key.value(), &self.validation).map_err(|e| {
442+
tracing::debug!(kid = %kid, error = %e, "JWT validation failed");
443+
Error::Jwt(e)
444+
})
432445
} else {
446+
tracing::warn!(kid = %kid, "JWT key ID not found in cache");
433447
Err(Error::KeyNotFound(Some(kid.clone())))
434448
}
435449
} else {
450+
tracing::warn!("JWT token has no key ID (kid)");
436451
Err(Error::KeyNotFound(None))
437452
}
438453
})

0 commit comments

Comments
 (0)