Back to Blog
SecurityJuly 2, 20264 min read

JWKS Security: Preventing OIDC Discovery Endpoints Signature Forgery

JWKS security is critical for OIDC integrity. Learn how to prevent signature forgery and key manipulation at your discovery endpoints in Node.js and PHP.

JWKSOIDCAPI SecurityNode.jsPHPAuthenticationCybersecuritySecurityWebBackend

During a recent security audit, I found an OIDC provider exposing its JWKS endpoint with zero caching headers and, worse, no integrity checks on the upstream key source. It’s a common oversight: developers treat the jwks_uri as a static configuration file rather than a dynamic, high-stakes security component. If an attacker can manipulate your JWKS, they can inject their own public keys, forge signatures, and effectively bypass authentication for your entire ecosystem.

When we talk about JWKS (JSON Web Key Set), we’re talking about the backbone of trust for your OIDC implementation. If your application blindly trusts whatever the discovery endpoint returns, you’re leaving the door open for signature forgery.

Understanding the Risk

The OIDC discovery process is supposed to be simple. Your client hits the .well-known/openid-configuration endpoint, finds the jwks_uri, and fetches the keys. The problem arises when the source of those keys is compromised or when your client-side implementation doesn't enforce strict validation.

If you’re managing your own identity provider or even just consuming tokens, failing to validate these keys is akin to leaving your house keys under the doormat. We previously explored how to handle JWT Security: Preventing Signature Bypass and Algorithm Confusion to ensure tokens aren't tampered with, but if the keys themselves are suspect, the signature validation logic becomes moot.

Hardening Your JWKS Implementation

To secure your OIDC security posture, you must treat the JWKS as a sensitive configuration resource.

  1. Cache with TTL and Integrity: Don't fetch the JWKS on every request. Cache it, but ensure your cache is immutable once fetched. I usually set a TTL of around 1 hour.
  2. Strict Origin Validation: Ensure the jwks_uri points to a trusted, hardcoded domain. Never allow this to be configurable via environment variables that could be manipulated.
  3. Key ID (kid) Matching: Always filter your key set by the kid provided in the JWT header. If the kid isn't in your trusted set, reject the token immediately.

Implementation in Node.js

In Node.js, I prefer using jwks-rsa to handle the heavy lifting. It’s mature and handles the caching and key rotation logic correctly.

JAVASCRIPT
const jwksClient = require(CE9178">'jwks-rsa');

const client = jwksClient({
  jwksUri: CE9178">'https://auth.yourdomain.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 5
});

// Always verify the kid matches one of the known keys
const getKey = (header, callback) => {
  client.getSigningKey(header.kid, (err, key) => {
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
};

Implementation in PHP

In PHP, specifically when working with Laravel or Symfony, don't roll your own parser. Use lcobucci/jwt or web-token/jwt-framework. If you're building a custom OIDC client, ensure you're validating the iss (issuer) claim before you even look at the signature.

StrategyBenefitRisk of Omit
Static JWKS URLPrevents discovery injectionPotential for MITM
Key CachingReduces latency/loadStale keys on rotation
kid VerificationEnsures correct key usageSignature forgery
Signature Algorithm CheckPrevents 'none' attackAlgorithm confusion

Preventing Signature Forgery

API security isn't just about TLS; it's about verifying the chain of trust. If you're using an external OIDC provider, ensure they provide an x509_cert_url or a signed JWKS. If the keys are served over plain HTTP, stop immediately.

I once worked on a project where we used a sidecar container to fetch the JWKS and update a shared volume. This isolated the network risk—if the main application was compromised, the attacker couldn't easily reach out to the identity provider to inject a malicious key. We also had to look at Preventing OAuth2 Dynamic Client Registration Vulnerabilities to ensure that the identity provider itself couldn't be tricked into registering rogue clients that might influence the JWKS discovery process.

Common Questions

Q: How often should I rotate keys? A: That depends on your threat model, but a 30-day rotation is standard. Ensure your client handles key rollover gracefully by attempting a refresh if a kid is unknown.

Q: Should I allow custom JWKS URLs? A: Never. Hardcode the JWKS URI in your application configuration. If you need multi-tenant support, use a strict whitelist of known-good issuers.

Q: What if the JWKS endpoint is down? A: Use a stale-while-revalidate caching strategy. Serve the last known good key set while attempting an asynchronous refresh.

I’m still not entirely satisfied with how most OIDC libraries handle key rotation under heavy load. If you’re seeing high latency during key refreshes, you might need to move the fetching process to a background job rather than doing it in the request-response cycle. It’s a trade-off between immediate consistency and system availability, but in security, consistency usually wins.

Similar Posts