{"record":{"id":"cf0e6c83f73c1a3f","repo":"clockworklabs/SpacetimeDB","slug":"error-fetching-public-key-for-issuer-raw-issuer","errorCode":null,"errorMessage":"Error fetching public key for issuer {raw_issuer}","messagePattern":"Error fetching public key for issuer (.+?)","errorType":"exception","errorClass":"TokenValidationError::Other","httpStatus":null,"severity":"error","filePath":"crates/core/src/auth/token_validation.rs","lineNumber":236,"sourceCode":"        let keys = key_or_error?;\n        let validator = JwksValidator {\n            issuer: raw_issuer.into(),\n            keyset: keys,\n        };\n        Ok(Arc::new(validator))\n    }\n}\n\n#[async_trait]\nimpl TokenValidator for CachingOidcTokenValidator {\n    async fn validate_token(&self, token: &str) -> Result<SpacetimeIdentityClaims, TokenValidationError> {\n        let raw_issuer = get_raw_issuer(token)?;\n        log::debug!(\"Getting validator for issuer {}\", raw_issuer.clone());\n        let validator = self\n            .cache\n            .get(String::from(raw_issuer.clone()).into())\n            .await\n            .ok_or_else(|| anyhow::anyhow!(\"Error fetching public key for issuer {raw_issuer}\"))?;\n        validator.validate_token(token).await\n    }\n}\n\n// This is a token validator that uses OIDC to validate tokens.\n// This will look up the public key for the issuer and validate against that key.\n// This currently has no caching.\npub struct OidcTokenValidator;\n\n// Get the issuer out of a token without validating the signature.\nfn get_raw_issuer(token: &str) -> Result<Box<str>, TokenValidationError> {\n    let mut validation = Validation::new(jsonwebtoken::Algorithm::ES256);\n    validation.set_required_spec_claims(&REQUIRED_CLAIMS);\n    validation.validate_aud = false;\n    // We are disabling signature validation, because we need to get the issuer before we can validate.\n    validation.insecure_disable_signature_validation();\n    let data = decode::<IncomingClaims>(token, &DecodingKey::from_secret(b\"fake\"), &validation)?;\n    Ok(data.claims.issuer)","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/core/src/auth/token_validation.rs#L218-L254","documentation":"CachingOidcTokenValidator extracts the raw issuer from the token, then fetches that issuer's JWKS public keys through an async cache. If the fetch fails — network error, unreachable OIDC endpoints, or an issuer the node does not recognize — the cache returns None and this error is produced, because validation cannot proceed without the issuer's keys.","triggerScenarios":"The server cannot reach the issuer's .well-known/openid-configuration or its jwks_uri (DNS failure, blocked egress, TLS error); the OIDC provider is down; the token's iss points to an issuer the node is not configured to trust.","commonSituations":"Self-hosted nodes in containers without outbound network access; corporate proxies blocking the identity provider; misconfigured issuer allow-lists; IdP outages.","solutions":["From the server host, verify reachability: `curl <iss>/.well-known/openid-configuration` and the jwks_uri it lists.","Open outbound firewall/proxy access to the identity provider domains.","Confirm the server's issuer allow-list includes the token's iss value.","Retry after the provider recovers — JWKS fetch failures are often transient."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"async function issuerReachable(issuer: string): Promise<boolean> {\n  try {\n    const res = await fetch(`${issuer.replace(/\\/$/, '')}/.well-known/openid-configuration`, { signal: AbortSignal.timeout(5000) });\n    return res.ok;\n  } catch { return false; }\n}\nif (!(await issuerReachable(iss))) throw new Error(`OIDC issuer ${iss} unreachable — check egress/DNS`);","typeGuard":null,"tryCatchPattern":"for (let attempt = 1; attempt <= 5; attempt++) {\n  try { return await validateOidcToken(token); }\n  catch (e) {\n    if (!String(e).includes('Error fetching public key')) throw e;\n    await sleep(2 ** attempt * 100); // JWKS fetch is network-bound: back off and retry\n  }\n}","preventionTips":["Allow-list identity provider domains in firewall/egress rules.","Add a startup health check for the OIDC discovery endpoint.","Cache JWKS results with TTL so transient IdP blips don't fail every request."],"tags":["spacetimedb","oidc","jwks","network","auth"],"backgroundTag":"jwks-fetch-failed","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}