# Passwords, codes and two-factor

> Password, passwordless one-time codes by email or SMS, authenticator-app two-factor and recovery codes.

Source: https://9in.io/docs/sign-in-methods/

Everything here is set in the console under **Login & Sign-Up** and **Security**, and applies to every application of the tenant. Your apps don't change: they redirect to the hosted page, which shows what's turned on.

## Identifiers

What people type to sign in: **email** (on by default), **username** and **phone number**. With several turned on, one field accepts any of them. For each identifier you choose whether it signs in with a **password**, a **one-time code**, or both. Phone numbers can be limited to some country codes.

## Passwords

Passwords are hashed with argon2id. **Password rules** (length, required character kinds) apply when people choose a password. Imported hashes (bcrypt, PBKDF2, Firebase scrypt…) keep working and are upgraded to argon2id at the next sign-in.

**Password recovery** sends a one-time code by email (or SMS for phone accounts) and lets the user pick a new password. You can send people to your own page instead (**Login & Sign-Up → Sign-In → Use your own recovery page instead**).

## One-time codes (passwordless)

With one-time codes on, users get a 6-digit code (6 to 8, configurable) by email or text message instead of typing a password. Codes expire after 10 minutes (configurable, 1 to 60) and allow 5 attempts. If an account has both an email and a phone number, the user picks where to receive it.

- **Email** codes are sent by 9in, with your logo and wording ([Emails and texts](https://9in.io/docs/emails-and-texts/)).
- **SMS** codes go through 9in's Twilio account on paid plans, or through your own Twilio account on any plan (**Security → SMS delivery**), with a sender number or a Twilio Verify service ([Emails and texts](https://9in.io/docs/emails-and-texts/#text-messages)). Limit the countries texts may go to; it's the main defence against SMS pumping fraud.
- With [your own user database](https://9in.io/docs/own-database/), 9in sends you an `otp.requested` event and you deliver the code yourself.

In the sandbox, codes are always `000000` and nothing is sent.

## Sign-up

**Login & Sign-Up → Sign-Up** turns self-registration on or off, chooses the fields the form asks for (name, username, phone, birthdate…, each optional or required, plus your own [custom fields](https://9in.io/docs/users/#custom-profile-fields)) and whether people are signed in straight after. You can also send people to your own sign-up page.

Open the sign-up form directly with `prompt=create` on the authorization request (`auth.signup()` in the SDK).

## Two-factor authentication

Two-factor uses an **authenticator app** (Google Authenticator, 1Password, Authy… any TOTP app, 6 digits every 30 seconds) after the first factor, with **10 single-use recovery codes** for when the phone is lost. **Security → Two-factor authentication**:

| Policy | What happens |
|---|---|
| Off | Never asked |
| Optional (default) | Users turn it on from their account page; once on, it's asked at every sign-in |
| Required | Everyone sets it up at their next sign-in |

It applies to every sign-in method, including social, enterprise and login links. The ID token's `amr` claim lists how the user signed in: `pwd` (password), `otp` (one-time code), `social`, `fed` (enterprise), `link` (login link), plus `mfa` after a second factor. Access tokens don't carry `amr`; your app can check the ID token before calling sensitive APIs, or the tenant can require two-factor for everyone.

Users manage two-factor on their account page (`/account`). For someone who lost their phone and their recovery codes, an admin opens the user in the console and chooses **Reset 2FA**; the reset is recorded in the activity log, and if two-factor is required they set it up again at their next sign-in.

## Brute-force protection

After 10 failed attempts (configurable) an account and the IP address are locked for 15 minutes (configurable). Bot protection with Cloudflare Turnstile can be added to sign-up, password recovery and sign-in (always, or only after failures). See [Sessions and security](https://9in.io/docs/security/).

## Legacy grants

For applications that can't use redirects yet, the **password grant** (resource owner password credentials) can be turned on per first-party application, and the **implicit flow** per application. Both are off by default and not recommended: they bypass bot protection and single sign-on. The password grant also refuses users who have two-factor turned on (and everyone when the tenant requires it), with `invalid_grant`, because it can't ask for a second factor. Prefer the authorization code flow.
