---
title: "Never remove the last way to sign in · Agent Wranglers"
url: https://agentwranglers.se/playbook/accounts/sign-in-methods/last-sign-in-method
updated: 2026-10-20
licence: "All rights reserved"
---

# Never remove the last way to sign in

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Sign-in methods](https://agentwranglers.se/playbook/accounts/sign-in-methods)
- Rule: Let people add and remove sign-in methods, but refuse to remove the last one. Never link a provider login to an account by email alone.
- How: List every passkey, password, provider login and second factor on one settings page, with the date each was added and last used. Count the remaining methods inside the same transaction as the removal. Count only methods that sign someone in alone, because a second factor cannot. To link a provider, the person signs in to the existing account first, then connects it. Ask for fresh proof first, as [ask again before changing how someone logs in](https://agentwranglers.se/playbook/adversarial-testing/access-and-accounts/reauthenticate-sensitive-changes) says.
- Check: On a test account with one passkey only, call the remove endpoint for it. The answer is 409 and the passkey stays. Sign in with a new provider login whose email matches an existing account. No link happens until that account signs in.

Example:

```js
const LAST_METHOD = 'You need at least one way to sign in. Add another before removing this one.';

export async function removeSignInMethod(db, userId, methodId) {
  await db.tx(async tx => {
    await tx.none('SELECT 1 FROM users WHERE id = $1 FOR UPDATE', [userId]);
    const { n } = await tx.one('SELECT count(*)::int AS n FROM sign_in_methods WHERE user_id = $1', [userId]);
    if (n <= 1) throw new ConflictError(LAST_METHOD);
    await tx.none('DELETE FROM sign_in_methods WHERE id = $1 AND user_id = $2', [methodId, userId]);
  });
}
```

```python
LAST_METHOD = "You need at least one way to sign in. Add another before removing this one."

def remove_sign_in_method(conn, user_id: str, method_id: str) -> None:
    with conn.transaction():
        conn.execute("SELECT 1 FROM users WHERE id = %s FOR UPDATE", (user_id,))
        (count,) = conn.execute("SELECT count(*) FROM sign_in_methods WHERE user_id = %s", (user_id,)).fetchone()
        if count <= 1:
            raise Conflict(LAST_METHOD)
        conn.execute("DELETE FROM sign_in_methods WHERE id = %s AND user_id = %s", (method_id, user_id))
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/sign-in-methods/last-sign-in-method`.

- Previous: [Weigh email links before making them the only way in](https://agentwranglers.se/playbook/accounts/sign-in-methods/email-sign-in-trade-offs)
- [Sign-in methods](https://agentwranglers.se/playbook/accounts/sign-in-methods)
