{"record":{"id":"06b932ba6e3d1ef6","repo":"passbolt/passbolt_api","slug":"unable-to-decode-jwt-token","errorCode":null,"errorMessage":"Unable to decode JWT token.","messagePattern":"Unable to decode JWT token\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Utility/OpenId/BaseIdToken.php","lineNumber":83,"sourceCode":"        unset($this->values['id_token']);\n\n        $keys = $provider->getJwtVerificationKeys();\n        try {\n            /**\n             * To fix \"Firebase\\JWT\\BeforeValidException: Cannot handle token prior\" error.\n             *\n             * @link https://github.com/googleapis/google-api-php-client/issues/1630\n             * @link https://stackoverflow.com/questions/53658600/uncaught-exception-firebase-jwt-beforevalidexception-with-message-cannot-hand\n             */\n            JWT::$leeway = Configure::read('passbolt.plugins.sso.security.jwtLeeway');\n\n            $tokenClaims = (array)JWT::decode($this->idToken, $keys);\n        } catch (Exception $exception) {\n            if (Configure::read('passbolt.plugins.sso.debugEnabled')) {\n                Log::error('idToken => ' . json_encode($this->idToken));\n            }\n\n            throw new BadRequestException(__('Unable to decode JWT token.'), 400, $exception);\n        }\n\n        try {\n            $this->assertTokenClaims($tokenClaims);\n        } catch (BadRequestException $exception) {\n            if (Configure::read('passbolt.plugins.sso.debugEnabled')) {\n                Log::error('tokenClaims => ' . json_encode($tokenClaims));\n            }\n\n            throw $exception;\n        }\n\n        $this->idTokenClaims = $tokenClaims;\n    }\n\n    /**\n     * Validate the access token claims from an access token you received in your application.\n     * Note: nbf and exp claims are validated in JWT::decode","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Utility/OpenId/BaseIdToken.php#L65-L101","documentation":"After extracting the id_token string, BaseIdToken decodes and verifies it with firebase/jwt (JWT::decode using the provider's verification keys). Any failure — bad signature, expired token, malformed JWT — is caught and rethrown as BadRequestException('Unable to decode JWT token.') with the original exception chained. When `passbolt.plugins.sso.debugEnabled` is set, the raw token is logged before throwing.","triggerScenarios":"JWT::decode throwing during __construct: signature verification failure (wrong JWKS/keys), token expired (exp in the past), malformed token string, or unsupported algorithm (e.g. RS256 token validated with HS256 key material).","commonSituations":"Clock skew making freshly issued tokens appear expired; provider rotated signing keys and cached JWKS is stale; token truncated/URL-encoded in transit; testing with a token from a different provider/environment.","solutions":["Enable `passbolt.plugins.sso.debugEnabled` to log the failing token and inspect its payload at jwt.io (check exp, alg, iss).","Synchronize server clock via NTP — expired/nbf claims are the most frequent decode failure.","Clear any cached JWKS/verification keys so the provider's current signing keys are fetched.","Confirm the token is passed unmodified (no truncation, URL-decoding, or quoting issues) from client to server."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"$parts = explode('.', $idToken);\nif (count($parts) !== 3) {\n    throw new RuntimeException('Malformed JWT: expected 3 dot-separated segments');\n}","typeGuard":"function looksLikeJwt(mixed $token): bool {\n    return is_string($token) && preg_match('/^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]*$/', $token) === 1;\n}","tryCatchPattern":"try {\n    $idTokenObj = new AzureIdToken($provider, ['id_token' => $jwt]);\n} catch (BadRequestException $e) {\n    // getPrevious() holds JWT::decode failure: signature/exp/alg — inspect it\n}","preventionTips":["Keep server clocks NTP-synchronized; exp/nbf failures dominate decode errors","Fetch JWKS keys fresh on failure; don't cache provider signing keys indefinitely","Pass the JWT string through unmodified (no URL-encoding or truncation)"],"tags":["jwt","openid","sso","signature-verification"],"backgroundTag":"invalid-json-response","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}