Skip to main content

Login identifier column has no unique constraint

A login table (one that stores a password) has an email or username column with no unique constraint, and the code looks accounts up by it. A sign-up race or a case-variant duplicate lets two accounts share one identifier, so one user may sign in to, reset, or receive mail for another's account.

What Vulkro detects​

Rule schema-login-identifier-not-unique, default severity Medium, risk category Authentication.

Vulkro reads every model and migration for the table in the database schema. The rule fires when no unique constraint or unique index covers the login identifier and the code looks an account up by it.

Non-compliant code​

// `email` is the login identifier but has no unique constraint
const accountSchema = new mongoose.Schema({
email: { type: String, required: true, lowercase: true },
passwordHash: { type: String, required: true },
});

const account = await Account.findOne({ email: req.body.email.toLowerCase() });

Compliant code​

const accountSchema = new mongoose.Schema({
email: { type: String, required: true, lowercase: true },
passwordHash: { type: String, required: true },
});

accountSchema.index({ email: 1 }, { unique: true });

How to fix​

  • Add a unique constraint or index on the login identifier, case-insensitive where supported (citext, a lower(email) unique index, COLLATE NOCASE).
  • Normalise the value before writing it, and remove duplicate rows before the migration runs.
  • Do not rely on an application check alone: only the database closes the race.

References​

See also: Database schema discovery, API2 Broken Authentication.