Diagnosing Ory Kratos Login Failures: The invalid_credentials Error
A diagnostic checklist for Ory Kratos self-service login failing with invalid_credentials: read the debug logs, verify the identity and schema identifiers, check the password hasher config, and rule out cookie domain issues.
30 Aug 2026, 11:30 UTC

What invalid_credentials actually means
When a self-service login against Ory Kratos returns invalid_credentials, the flow reached the credential verification stage and failed there. It is not a network problem, a CSRF problem, or a session problem — Kratos found (or failed to find) an identity and the password check did not succeed. That narrows the search considerably, but the error message is deliberately vague to avoid leaking whether an account exists, so the real cause lives in your server-side logs and configuration, not in the API response.
This guide assumes a self-hosted Kratos v1.x deployment with password-based login. Exact config keys and log formats can shift between minor versions, so verify against the docs for the version you run.
Cause and diagnostic table
| Likely cause | How it shows up | Where to confirm |
|---|---|---|
| Identity does not exist | Login fails for every password; admin lookup finds nothing | Admin API identity list |
| Schema field mismatch | Login fails after a schema change; identifier never resolves | Identity schema vs. submitted payload |
| Hashing algorithm mismatch | All logins fail after migration or upgrade | kratos.yaml hashers config vs. stored hashes |
| Wrong identifier submitted | Client sends email but schema identifier is username | Login flow UI nodes / request payload |
| Cookie/domain misconfiguration | Flow expires or browser never completes login | Browser dev tools, serve.public domain settings |
Step 1: Read the Kratos logs first
Before changing anything, reproduce the failure and read the Kratos log output. Temporarily raise the log level in kratos.yaml:
log:
level: debug
format: jsonRestart Kratos (or apply your config reload mechanism) and trigger one login attempt. The log entry for the failed login includes a reason or cause field indicating whether the identifier resolved to an identity and whether password comparison ran. Two cautions: debug logging can record sensitive request data, so enable it only for the reproduction window, and never surface these details to end users.
Step 2: Confirm the identity exists and the identifier resolves
Query the admin API from a host that can reach the admin port (typically 4434), using an operator account or network-restricted shell:
curl -s "http://localhost:4434/admin/identities" \
| jq '.[] | {id, schema_id, traits}'Check that an identity exists with the email or username the user typed, and that the trait holding the identifier is populated exactly as expected — watch for case differences and stray whitespace. If the identity is missing entirely, the problem is upstream in your registration flow, not in login.
Step 3: Check the schema identifier configuration
Kratos only looks up logins by traits marked as identifiers in the identity schema. In your schema JSON, the relevant section looks like:
"ory.sh/kratos": {
"credentials": {
"password": { "identifier": true }
}
}If the schema was edited and the identifier: true marker was dropped or moved to a different field, the login lookup silently matches nothing. Also confirm the login request itself sends the identifier under the field name the flow expects — inspect the payload your client submits (browser dev tools, Network tab) and compare it against the flow's UI nodes returned by GET /self-service/login/api.
Step 4: Verify the password hashing configuration
If every login fails — including accounts you know worked before a migration or upgrade — suspect the hasher. In kratos.yaml:
hashers:
algorithm: bcrypt
bcrypt:
cost: 12Kratos stores the algorithm alongside each hash, so a config change normally does not break existing passwords — but a botched data migration that re-hashed or re-imported credentials with a different algorithm will. Inspect a stored credential via the admin API (GET /admin/identities/{id}?include_credential=password, admin access required) and check the hash prefix: $2a$/$2b$ indicates bcrypt, $argon2id$ indicates argon2. If the stored hashes do not match what you expect, the fix is a password reset for affected users — never attempt to "convert" hashes in place.
Step 5: Rule out cookie and domain issues
If the flow fails before credential submission — for example the flow ID is rejected or the session never establishes after a successful-looking login — check that serve.public.base_url, serve.public.cookie.domain, and your app's domain align. A cookie set for auth.example.com will not be sent from app.example.org. In browser dev tools, confirm the login POST carries the flow cookie and that the response sets a session cookie on the expected domain.
Fixes mapped to findings
- Missing identity: fix the registration flow or re-import the account; no login-side change helps.
- Schema mismatch: restore
identifier: trueon the correct trait and update clients to submit the matching field name. - Hash mismatch: trigger the account recovery flow so users set new passwords hashed with the configured algorithm.
- Cookie/domain: align
base_urland cookie domain with the actual public hostname; clear stale cookies and retest.
When to escalate
Escalate to the Ory community or your platform team when: the debug logs show the password comparison running and failing for a credential you have just reset through the recovery flow; identities appear correct in the admin API but lookups still miss; or failures correlate with a specific node in an HA deployment (suggesting inconsistent config or database replicas). Include the Kratos version, the redacted debug log entry, and the schema identifier block in the report — and revert the log level to info once you have what you need.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.