roundcube/roundcubemail · warning · RuntimeException
OIDC: event has non-empty "nonce"
Error message
OIDC: event has non-empty "nonce"
What it means
Per the OIDC Back-Channel Logout spec, a logout token MUST NOT contain a 'nonce' claim (that would make it replayable as an ID token). The handler rejects any token carrying a nonce.
Solutions
- Fix the IDP so it emits a real logout token (no nonce, has 'events' claim) to the back-channel URI.
- Confirm the back-channel logout URI is set to the oauth_backchannel action, not the login/token endpoints.
- Inspect the incoming JWT payload and remove 'nonce' if generating tokens in a test harness.
- Update the IDP version if it has a known bug including nonce in logout tokens.
Example fix
// before (ID token sent as logout token)
{"iss":"https://idp","aud":"rc","sub":"u1","nonce":"abc123"}
// after
{"iss":"https://idp","aud":"rc","sub":"u1","events":{"http://schemas.openid.net/event/backchannel-logout":{}}} Defensive patterns
Strategy: validation
Validate before calling
$body = json_decode(base64_decode(strtr(explode('.', $jwt)[1], '-_', '+/')), true);
if (isset($body['nonce'])) { /* reject: nonce present, not a logout token */ } Type guard
function isNonceFree(array $claims): bool { return !array_key_exists('nonce', $claims); } Try / catch
try { $handler->run(); } catch (\RuntimeException $e) { http_response_code(400); error_log('backchannel: ' . $e->getMessage()); } Prevention
- Never wire ID-token issuance into the back-channel logout URI.
- Verify logout tokens contain 'events' and 'sub' but no 'nonce'.
- Test with the IDP's documented logout-token example.
When it happens
Trigger: run() receives a JWT whose payload includes a 'nonce' key — typically an ID token was sent to the back-channel logout endpoint instead of a proper logout token.
Common situations: Misconfigured IDP wiring the token/ID-token endpoint output into back-channel logout; custom middleware forwarding access tokens; developer testing with an ID token copied from a login flow.
Related errors
- OIDC: Handle only logout events
- OIDC: event has no "sub"
- Failed to validate JWT: invalid azp value
- Failed to validate JWT: invalid aud value
- Failed to validate JWT: missing aud/azp value
AI-assisted analysis of roundcube/roundcubemail@4b54c2acfb (2026-09-14).
Data as JSON: /api/errors/323a4fd229c6c6fd.
Report an issue: GitHub.
Appendix: source
Thrown at program/actions/login/oauth_backchannel.php:69
"iss":"https://....", // issuer identifier
"aud":"my client id", // audience = client id
"sub":"82c8f487-df95-4960-972c-4e680c3c72f5", // subject
"sid":"28101815-0017-4ade-a550-e054bde07ded", // session
"events":{"http://schemas.openid.net/event/backchannel-logout":[]}
}
*/
// Validation: https://openid.net/specs/openid-connect-backchannel-1_0.html#rfc.section.2.6
// Note: 'typ' is recommended, not required, so we allow untyped tokens
if (!empty($event['header']['typ']) && $event['header']['typ'] !== 'logout+jwt') {
throw new \RuntimeException('OIDC: Handle only logout events');
}
if (!isset($event['sub'])) {
throw new \RuntimeException('OIDC: event has no "sub"');
}
if (isset($event['nonce'])) {
throw new \RuntimeException('OIDC: event has non-empty "nonce"');
}
$rcmail->oauth->log_debug('backchannel: logout event received, schedule a revocation for token\'s sub: %s', $event['sub']);
$rcmail->oauth->schedule_token_revocation($event['sub']);
http_response_code(200); // 204 works also
header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: no-store');
echo '{}';
exit;
} catch (\Exception $e) {
rcube::raise_error($e, true);
$answer['error_description'] = 'Error decoding JWT';
}
} else {
rcube::raise_error(sprintf('oidc backchannel called from %s without any parameter', rcube_utils::remote_addr()), true);
}
View on GitHub (pinned to 4b54c2acfb)