thephpleague/oauth2-server · error · OAuthServerException
6
6
Error message
The user credentials were incorrect.
What it means
OAuthServerException::invalidCredentials() produces an 'invalid_credentials' error (code 6) with the message 'The user credentials were incorrect.' The password grant throws it when UserRepository::getUserEntityByUserCredentials returns null/false - i.e. no UserEntity matched the supplied username/password for this grant type and client. Before throwing, it emits a USER_AUTHENTICATION_FAILED event so apps can audit or throttle.
Solutions
- Verify the username/password pair is correct and the account exists and is active
- Confirm your UserRepository returns a UserEntityInterface and hashes are compared with password_verify against the stored hash
- Listen for the USER_AUTHENTICATION_FAILED event to log failed attempts and detect lockouts/brute force
- Return null rather than throwing from getUserEntityByUserCredentials so the library surfaces the proper OAuth error
Example fix
// before
$user = $this->userRepository->getUserEntityByUserCredentials($username, $password, 'password', $client);
if ($user === null) { return null; }
// after (repository side)
if (password_verify($password, $storedHash)) {
return $userEntity; // must implement UserEntityInterface
}
return null; // library then throws invalidCredentials with code 6 Defensive patterns
Strategy: try-catch
Validate before calling
if (empty($username) || empty($password)) {
// fail fast client-side before invoking the grant
throw new \InvalidArgumentException('Credentials required');
} Type guard
function credentialsPresent(?string $u, ?string $p): bool {
return $u !== null && $u !== '' && $p !== null && $p !== '';
} Try / catch
try {
$token = $server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $e) {
if ($e->getMessage() === 'The user credentials were incorrect.') {
// show generic auth-failure message; consider rate limiting
return $e->generateHttpResponse($response->withStatus(401));
}
throw $e;
} Prevention
- Return a UserEntityInterface from getUserEntityByUserCredentials on success, null on failure
- Compare passwords with password_verify against the stored hash
- Emit/listen to USER_AUTHENTICATION_FAILED for auditing and lockout logic
- Keep hashing algorithms consistent across user migrations
When it happens
Trigger: validateUser in PasswordGrant: getUserEntityByUserCredentials($username, $password, grantType, $client) returns something that is not a UserEntityInterface - wrong password, unknown user, credentials valid only for another grant type, or the repository filtering by client.
Common situations: User typed the wrong password or an email/username variant that isn't registered; password hashing mismatch after migrating users (bcrypt vs argon2); custom user repository returning null for grant_type scoping it doesn't support; locked or soft-deleted user accounts; test fixtures not seeded in a new environment.
Related errors
AI-assisted analysis of thephpleague/oauth2-server@9d2f6fc0a0 (2026-09-15).
Data as JSON: /api/errors/08b2e44e2e0e63bd.
Report an issue: GitHub.
Appendix: source
Thrown at src/Grant/PasswordGrant.php:101
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client): UserEntityInterface
{
$username = $this->getRequestParameter('username', $request)
?? throw OAuthServerException::invalidRequest('username');
$password = $this->getRequestParameter('password', $request)
?? throw OAuthServerException::invalidRequest('password');
$user = $this->userRepository->getUserEntityByUserCredentials(
$username,
$password,
$this->getIdentifier(),
$client
);
if ($user instanceof UserEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
}
return $user;
}
/**
* {@inheritdoc}
*/
public function getIdentifier(): string
{
return 'password';
}
}
View on GitHub (pinned to 9d2f6fc0a0)