Replace (string) casts on JSON payload values with is_string guards#42
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #42 +/- ##
===========================================
Coverage 100.00% 100.00%
- Complexity 62 64 +2
===========================================
Files 1 1
Lines 172 175 +3
===========================================
+ Hits 172 175 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jekuaitk
reviewed
May 12, 2026
| throw new CacheException('Required config key not defined: '.$key); | ||
| } | ||
| if (!is_string($configuration[$key])) { | ||
| throw new CacheException(sprintf('OIDC discovery document value for "%s" is not a string (got %s)', $key, get_debug_type($configuration[$key]))); |
Collaborator
There was a problem hiding this comment.
I know the existing code throws a CacheException here, but surely theres a more fitting exception?
Collaborator
Author
There was a problem hiding this comment.
Added new MetadataException
<?php
namespace ItkDev\OpenIdConnect\Exception;
/**
* Thrown when the IdP-returned OIDC discovery metadata does not conform to the
* OIDC Discovery spec — for example, a required key is missing or has the
* wrong type. Distinct from `JsonException` (the document didn't even parse)
* and `CacheException` (the cache layer failed): the JSON parsed fine, the
* cache is fine, but the document's contents are structurally invalid.
*
* Consumers typically can't recover by retrying — the IdP needs to fix the
* payload — so a `catch (MetadataException $e)` block normally logs + alerts.
*/
class MetadataException extends \RuntimeException implements OpenIdConnectExceptionInterface
{
}Two `(string)` casts in `OpenIdConfigurationProvider` coerced JSON-decoded mixed values into strings: the JWK `kid` lookup and the OIDC discovery doc value lookup. Both upstream payloads specify the field as a string by spec (RFC 7517 §4.5 for `kid`; OIDC Discovery for the document values), but a malformed-payload case silently turned an int / bool / array into a useless string and produced confusing downstream failures rather than diagnosing the malformed input. Replace each cast with an `is_string` guard that throws an appropriate typed exception (`KeyException` and `CacheException` respectively). Both already implement `OpenIdConnectExceptionInterface`, so consumers catching the marker pick up the new throw paths without code changes. Adds two tests covering the new branches to preserve 100% coverage (151/151 lines). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`getConfiguration` has two distinct failure boundaries:
- `catch (InvalidArgumentException $e) → throw new CacheException(...)`
correctly maps PSR-6 cache-layer failures to `CacheException`.
- The inline `throw` for "required key missing" / "value is not a
string" was *also* using `CacheException`, but those validation
failures fire regardless of whether `$configuration` came from
cache or from a fresh `fetchJsonResource()` call — the IdP-
returned (or previously cached) JSON payload doesn't conform to
the OIDC Discovery schema. Calling that a "Cache" exception
misleads operators looking at logs.
Switch both validation throws to `JsonException`, the existing
concrete the library already uses for "JSON payload from the IdP is
malformed". The new check (added in this PR for the previously-
silent-coerced non-string value case) and the pre-existing
missing-key check now share the same type, since they're the same
failure category.
Both `CacheException` and `JsonException` implement
`OpenIdConnectExceptionInterface`, so consumers catching the marker
are unaffected. Consumers catching `CacheException` specifically for
the missing-key case will need to widen — flagged in the CHANGELOG.
Addresses the review comment on the original throw:
#42 (comment)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
47e288c to
a320df6
Compare
`JsonException` stretches its semantic when used for "the JSON parsed fine but the document doesn't conform to the OIDC Discovery spec" — the existing usage of `JsonException` is strictly for `json_decode` failures (the bytes aren't valid JSON). Mixing the two failure modes under one type means a consumer catching `JsonException` to retry on transient parse failures would incorrectly retry a malformed-discovery- document case (the IdP will keep returning the same bad payload — no retry will help). New `MetadataException extends \RuntimeException implements OpenIdConnectExceptionInterface` covers this distinct failure category. The two validation throws in `getConfiguration` (missing required key; non-string value at a required key) switch to it. `fetchJsonResource` still throws `JsonException` for actual parse failures, so the categories stay clean. Updates the `@throws` lists on `getBaseAuthorizationUrl`, `getEndSessionUrl`, `validateIdToken`, and `getConfiguration` to advertise the new transitively-thrown type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
jekuaitk
approved these changes
May 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Pre-existing follow-up flagged during PR #41's review. Two
(string)casts inOpenIdConfigurationProvidercoerced JSON-decoded mixed values into strings:$kid = (string) $key['kid'];— JWK key identifier (RFC 7517 §4.5 specifies it as a JSON string).$value = (string) $configuration[$key];— OIDC discovery document value (OIDC Discovery specifies these as strings).Both spec-compliant payloads work today. The cast silently masks the malformed-payload case: an int / bool / array gets coerced to a useless string (
"42","","Array") that flows downstream and produces a confusing assertion failure rather than diagnosing "the IdP returned a malformed payload at this key".What changes
Each cast is replaced with an
is_stringguard that throws an appropriate typed exception:kid→KeyException('JWK entry missing string "kid" (RFC 7517 §4.5)').CacheException(sprintf('OIDC discovery document value for "%s" is not a string (got %s)', $key, get_debug_type(...))).Both
KeyExceptionandCacheExceptionalready implementOpenIdConnectExceptionInterface(per the 5.0 contract), so consumers catching the marker pick up the new throw paths without code changes.Backward compatibility
Spec-compliant IdPs (every IdP we test against) are unaffected. The new throw paths only fire when the upstream payload violates the spec — and in that case the new behaviour is strictly better than silent coercion.
The two new throws share the same
OpenIdConnectExceptionInterfacemarker as their pre-existing siblings; existingcatch (OpenIdConnectExceptionInterface)blocks pick them up automatically. Documented under the 5.0 BREAKING section of the CHANGELOG.Test plan
task test:coverage— 89 tests; 100% coverage onOpenIdConfigurationProvider(24/24 methods, 151/151 lines including the two new throw lines).task analyze:php— PHPStan level 8, no errors.task lint:php— clean.task lint:markdown— clean.🤖 Generated with Claude Code