BookStackApp/BookStack · error · OidcInvalidTokenException
No valid subject value found in userinfo data
Error message
No valid subject value found in userinfo data
What it means
Per OpenID Connect spec v1.0 §5.3.2, the UserInfo response MUST include a non-empty string 'sub' claim. OidcUserinfoResponse::validate() throws this OidcInvalidTokenException when the 'sub' claim is absent, empty, or not a string, because the response cannot be trusted or correlated with the ID token.
Source
Thrown at app/Access/Oidc/OidcUserinfoResponse.php:40
$this->jwt = new OidcJwtWithClaims($response->getBody()->getContents(), $issuer, $keys);
$this->claims = $this->jwt->getAllClaims();
}
}
/**
* @throws OidcInvalidTokenException
*/
public function validate(string $idTokenSub, string $clientId): bool
{
if (!is_null($this->jwt)) {
$this->jwt->validateCommonTokenDetails($clientId);
}
$sub = $this->getClaim('sub');
// Spec: v1.0 5.3.2: The sub (subject) Claim MUST always be returned in the UserInfo Response.
if (!is_string($sub) || empty($sub)) {
throw new OidcInvalidTokenException("No valid subject value found in userinfo data");
}
// Spec: v1.0 5.3.2: The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token;
// if they do not match, the UserInfo Response values MUST NOT be used.
if ($idTokenSub !== $sub) {
throw new OidcInvalidTokenException("Subject value provided in the userinfo endpoint does not match the provided ID token value");
}
// Spec v1.0 5.3.4 Defines the following:
// Verify that the OP that responded was the intended OP through a TLS server certificate check, per RFC 6125 [RFC6125].
// This is effectively done as part of the HTTP request we're making through CURLOPT_SSL_VERIFYHOST on the request.
// If the Client has provided a userinfo_encrypted_response_alg parameter during Registration, decrypt the UserInfo Response using the keys specified during Registration.
// We don't currently support JWT encryption for OIDC
// If the response was signed, the Client SHOULD validate the signature according to JWS [JWS].
// This is done as part of the validateCommonClaims above.
return true;
}View on GitHub (pinned to 18f8469a1c)
Solutions
- Verify OIDC_USERINFO_ENDPOINT points at the correct userinfo URL of the IdP
- Inspect the raw userinfo response (curl with the access token) and check the 'sub' claim
- Update or patch the IdP to include a string 'sub' claim per OIDC Core §5.3.2
- If the IdP cannot be fixed, disable the userinfo endpoint so claims are read from the ID token
Example fix
// before (wrong endpoint, returns token introspection JSON without sub) OIDC_USERINFO_ENDPOINT=https://idp.example.com/introspect // after OIDC_USERINFO_ENDPOINT=https://idp.example.com/userinfo
Defensive patterns
Strategy: validation
Validate before calling
// Validate the userinfo payload manually before login flows:
$ui = json_decode($rawUserinfoBody, true);
if (!is_array($ui) || !isset($ui['sub']) || !is_string($ui['sub']) || $ui['sub'] === '') {
throw new DomainException('IdP userinfo response lacks a valid string sub claim');
} Try / catch
try {
auth()->attemptOidcLogin();
} catch (BookStack\Access\Oidc\OidcException $e) {
if (str_contains($e->getMessage(), 'Userinfo endpoint')) {
Log::error('IdP userinfo invalid', ['detail' => $e->getMessage()]);
abort(502, 'Identity provider userinfo response is not OIDC compliant');
}
throw $e;
} Prevention
- Verify the IdP follows OIDC Core §5.3.2 (sub always present)
- Point OIDC_USERINFO_ENDPOINT at the real userinfo URL
- Curl the userinfo endpoint with a real access token during setup
- Update IdP versions with known userinfo compliance bugs
When it happens
Trigger: validate() is called from getUserDetailsFromToken; $this->getClaim('sub') returns null, an empty string, or a non-string (number/object), so the is_string/empty guard fails.
Common situations: IdP violating the OIDC spec (omitting sub in userinfo), a broken reverse proxy mangling the JSON, custom/misconfigured userinfo endpoint URL in .env pointing at the wrong route, or an IdP returning an error payload that still parses as JSON.
Related errors
- Subject value provided in the userinfo endpoint does not mat
- Userinfo endpoint response validation failed with error: {$e
- Token audience value has ' . count($aud) . ' values, Expecte
- Token authorized party exists but does not match the expecte
- Missing token expiration time value
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/0262df50dc1ab0ba.
Report an issue: GitHub.