{"record":{"id":"408e9293251b6a67","repo":"thephpleague/oauth2-server","slug":"3-the-request-is-missing-a-required-parameter-includes-an","errorCode":"3","errorMessage":"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.","messagePattern":"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\\.","errorType":"http","errorClass":"OAuthServerException","httpStatus":400,"severity":"error","filePath":"src/Grant/RefreshTokenGrant.php","lineNumber":109,"sourceCode":"        $refreshToken = $this->issueRefreshToken($accessToken);\n\n        if ($refreshToken !== null) {\n            $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));\n            $responseType->setRefreshToken($refreshToken);\n        }\n\n        return $responseType;\n    }\n\n    /**\n     * @throws OAuthServerException\n     *\n     * @return array<string, mixed>\n     */\n    protected function validateOldRefreshToken(ServerRequestInterface $request, string $clientId): array\n    {\n        $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request)\n            ?? throw OAuthServerException::invalidRequest('refresh_token');\n\n        // Validate refresh token\n        try {\n            $refreshToken = $this->decrypt($encryptedRefreshToken);\n        } catch (Exception $e) {\n            throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e);\n        }\n\n        $refreshTokenData = json_decode($refreshToken, true);\n        if ($refreshTokenData['client_id'] !== $clientId) {\n            $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_CLIENT_FAILED, $request));\n            throw OAuthServerException::invalidRefreshToken('Token is not linked to client');\n        }\n\n        if ($refreshTokenData['expire_time'] < time()) {\n            throw OAuthServerException::invalidRefreshToken('Token has expired');\n        }\n","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/RefreshTokenGrant.php#L91-L127","documentation":"OAuthServerException::invalidRequest('refresh_token') is thrown when the refresh-token grant request has no refresh_token parameter at all (error code 3, 'invalid_request'). validateOldRefreshToken reads the refresh_token request parameter and, if absent, immediately throws this exception because a refresh grant cannot proceed without the token to decrypt.","triggerScenarios":"POST /token with grant_type=refresh_token but the body omits the refresh_token parameter — e.g. an empty form body, the token was never persisted client-side, or the parameter is sent under a different name.","commonSituations":"Client stores the refresh token but a refactor drops it from the token-refresh call; a proxy or middleware strips the body; storage layer returns null/undefined for the stored refresh token and it is serialized away; sending JSON body while the server expects application/x-www-form-urlencoded.","solutions":["Ensure the token refresh HTTP call includes refresh_token=<stored value> in the application/x-www-form-urlencoded body.","Check client-side storage for the refresh token before calling the endpoint; do not call the refresh endpoint if it is missing (redirect to login instead).","If using fetch/axios, verify the body is URL-encoded form data, not raw JSON, unless the server is configured for JSON.","Log the outgoing request body (minus secrets) to confirm the parameter name is exactly 'refresh_token'."],"exampleFix":"// before\nconst res = await fetch('/token', { method: 'POST', body: { grantType: 'refresh_token' } });\n\n// after\nconst body = new URLSearchParams({ grant_type: 'refresh_token', refresh_token: storedRefreshToken });\nconst res = await fetch('/token', { method: 'POST', body });","handlingStrategy":"validation","validationCode":"// before calling the token endpoint\nif (!storedRefreshToken || typeof storedRefreshToken !== 'string' || storedRefreshToken.length < 16) {\n  // skip refresh; go straight to login flow\n}","typeGuard":"function hasRefreshToken(v) {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":"try {\n  $tokens = $server->respondToAccessTokenRequest($request, $response, $ttl);\n} catch (OAuthServerException $e) {\n  if ($e->getCode() === 3) {\n    // refresh_token param missing: force re-authentication\n  }\n  throw $e;\n}","preventionTips":["Always build the refresh request body with URLSearchParams / http_build_query so the parameter cannot be dropped","Never invoke the refresh endpoint without first verifying the token is present in storage","Send application/x-www-form-urlencoded bodies, matching PSR-7 parsed-body expectations"],"tags":["oauth2","missing-parameter","refresh-token-grant","php"],"backgroundTag":"missing-required-argument","analyzedSha":"9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c","analyzedAt":"2026-09-15T22:33:30.452Z","contentChangedAt":"2026-09-15T22:33:30.452Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}