Sign-in methods
An overview of every way to sign in to Better IAM (passwords, magic links and codes, passkeys, federation, API keys, temporary tokens, and assumed roles) and how to enable and restrict them.
Better IAM supports several ways to sign in, because no single method suits everyone: passwords are familiar, emailed links avoid passwords altogether, passkeys resist phishing, enterprises want their own identity provider, and machines need keys. You choose which methods your deployment offers, and each organization (a ) can narrow that set with its tenant policy.
Whatever the method, a person's sign-in ends the same way: a , or an MFA challenge when a second factor is required (see how a sign-in works).
| Method | Calls | Session method | Enabled by | Guide |
|---|---|---|---|---|
| Email and password | auth.signIn, auth.signUp | password | emailPassword (default true) | Email and password |
| Magic link or emailed code | auth.startPasswordless, auth.finishPasswordless | passwordless-email | passwordlessEmail and sendEmail | Magic links and codes |
| SMS code | auth.startPasswordless, auth.finishPasswordless | passwordless-sms | passwordlessSms and sendSms | Magic links and codes |
| Passkey | auth.beginPasskeyAuthentication, auth.finishPasskeyAuthentication | passkey | passkeys | Passkeys |
| Federated (OAuth/OIDC, SAML) | The protocol packages, then protocolHost.completeAuthentication | federated | A protocol mount | OAuth sign-in, SAML |
| API key (service accounts) | Authorization: Bearer biam_key_... | none | Always available | API keys |
| Session token, session JWT, web identity | sts.getSessionToken, sts.assumeRoleWithWebIdentity | none | Always; JWTs need sts.jwt, web identity needs sts.webIdentity.enabled | Tokens and JWTs |
| Assumed role | roles.assume | none | A trust created by a root administrator | Temporary access |
The session's method reaches policies as principal.authMethod, so a policy can, for example, require a passkey
for a sensitive action.
Choose the methods you offer
Methods are switched on under authentication in the server options. Features that send messages need a delivery
callback: sendEmail hands a queued email to your mail provider, and sendSms does the same for text messages.
Construction fails with INVALID_CONFIG when an enabled email feature has no sendEmail, or SMS codes have no
sendSms, so a misconfiguration is caught at startup rather than when someone tries to sign in.
import { betterIam } from 'better-iam';
import { pwnedPasswords } from 'better-iam/auth';
export const iam = betterIam({
// database, secret, baseURL ...
authentication: {
sendEmail: async (message) => mailer.send(message), // deduplicate retries by message.id
sendSms: async (message) => sms.send(message),
emailPassword: true, // the default; false turns password sign-in off
passwordlessEmail: true, // magic links and emailed codes
passwordlessSms: true, // SMS codes
signUpEnabled: false, // the default
passkeys: { rpID: 'example.com', rpName: 'Acme Cloud' },
passwordPolicy: { isBreached: pwnedPasswords() },
},
});Passwords
Passwords remain the default for most products. auth.signIn({ tenantId, email, password, deviceToken? }) checks
the password and returns a session or an MFA challenge. Passwords are hashed with Argon2id and must be 12 to 1,024
characters long. A wrong password, an unknown address, and a disabled account all fail with the same
INVALID_CREDENTIALS, and unknown accounts perform the same hashing work, so responses never reveal which accounts
exist. emailPassword: false disables password sign-in, reset, and self-registration (FEATURE_DISABLED).
Email and password covers sign-in, verification, reset, change, hashing, and the absence of password-hash import.
Password screening
Length alone does not make a password safe. authentication.passwordPolicy sets a deployment-wide screen that
applies wherever a password is set: blockCommonPasswords (on by default) refuses well-known passwords, keyboard
walks, sequences, and passwords with fewer than five distinct characters (WEAK_PASSWORD); isBreached checks a
breach corpus (BREACHED_PASSWORD), and pwnedPasswords() from better-iam/auth provides a k-anonymity Have I Been
Pwned client; check adds your own rule. Organizations add minimum length, character classes, personal-information,
history (PASSWORD_REUSED), and maximum-age (PASSWORD_EXPIRED) rules on top.
See password policy and password rules.
Self-registration
Most business products add people by invitation, so self-registration is disabled by default.
authentication.signUpEnabled: true lets auth.signUp create an account in an existing tenant. It never creates an
identity in the root tenant (FORBIDDEN), never signs the person in, and, because requireEmailVerification
defaults to the value of signUpEnabled, queues a verify-email message and refuses sign-in until the address is
verified (EMAIL_UNVERIFIED). See sign up and
email verification.
Magic links and one-time codes
Passwordless sign-in proves the person controls an email address or phone number by sending a single-use secret
there. auth.startPasswordless sends a magic link or a six-digit code, and auth.finishPasswordless redeems it.
startPasswordless always answers { success: true }, links and codes are single-use and expire after five
minutes, SMS supports codes only and only to a verified phone number, and passwordless sign-in never creates or links
accounts by matching an address. The tenant's allowedMethods must include passwordless-email or
passwordless-sms.
Passkeys
sign people in with a device-bound or synced WebAuthn credential instead of a password. They cannot be phished, and they satisfy MFA on their own. With discoverable passkeys, the browser's autofill picks the account and no email is needed. See passkeys.
Federated sign-in
Enterprise customers want their people to sign in with the company's own identity provider, so that joining, leaving, and MFA are managed in one place. Federated sign-in delegates the proof to that provider through (OAuth/OIDC, including Google and GitHub) or .
The protocol packages handle the ceremony. When it succeeds, they call protocolHost.completeAuthentication, which
finishes like any other sign-in with the method federated: the tenant must allow the method, and root, tenant,
and deployment MFA requirements apply.
- Federation maps
(tenant, provider, issuer, subject)to an identity. A new subject with a verified email provisions an account (VERIFIED_EMAIL_REQUIREDwithout one). - An email that already belongs to an identity fails with
ACCOUNT_LINK_REQUIRED. The person signs in to the existing account and links the provider explicitly; an email collision is never treated as proof of ownership. mapAttributesstores provider attributes on the identity, validated againstpermissions.identityAttributes, and replaces them on every sign-in.
See OAuth sign-in and SAML.
Service accounts and API keys
Scripts, integrations, and other servers cannot type a password or answer an MFA prompt. They sign in with API keys
issued to by credentials.create. A key is an opaque bearer
token sent as Authorization: Bearer <key>; only its hash is stored, it is returned once, it expires (90 days by
default), it can be scoped below what its account may do, and it keeps the ceiling of the
that issued it. credentials.rotate, credentials.revoke,
credentials.update, and credentials.list({ unusedForMs }) keep keys clean, and a key used from a blocked network
is refused.
See API keys and service accounts and the credentials reference.
Temporary tokens and web identity
A script or CLI should not carry a person's browser session or a long-lived key around. sts.getSessionToken trades
either for a short-lived biam_sts_ token, optionally narrowed by a policy and stepped up with a fresh MFA code.
With sts.jwt, session tokens and role sessions can be signed JWTs that other services verify offline against
GET /api/iam/.well-known/jwks.json. With sts.webIdentity.enabled, CI jobs and workloads exchange their platform's OIDC
token for a role session, so no IAM secret is stored.
See Tokens and JWTs.
Assumed roles
Sometimes an identity in one tenant must act inside another: your support team working in a customer's organization, or a central automation managing many tenants. Rather than creating accounts everywhere, gives a short-lived session with one specific role in the target tenant.
It rests on a trust: an exact source-identity to target-role relationship that a root administrator creates with
trust.create. The source identity then calls roles.assume to receive a role session:
import { iam } from './iam';
const { token, session } = await iam.api.roles.assume(credential, {
tenantId: targetTenantId,
trustId,
durationSeconds: 900, // 15 minutes, the default; at least 60
});roles.assume returns the role session's bearer token (a biam_rol_ token), which the caller sends as
Authorization: Bearer to act in the target tenant. The HTTP handler never sets it as a cookie, so the caller's own
session stays intact.
- Assumption also requires the source identity's
iam:roles:assumepermission oniam/{roleId}. A trust may require MFA (the default) and an external ID. - Role sessions last 15 minutes by default and at most an hour, unless the trust's
maxSessionSecondsand the deployment'ssts.maxRoleSessionSecondsallow longer. They never outlive the source credential. - Role sessions cannot chain (
ROLE_CHAINING_DISABLED), cannot be started from an impersonation session, and are never recently authenticated, so they cannot perform sensitive operations. - Revoking the trust, the source session, the source identity, or a relevant grant authority ends the role session at its next use.
See role sessions for every trust and session option.
Restricting methods per tenant
Not every organization wants every method you offer. A company that runs its own identity provider may insist on
federated sign-in only. An organization's allowedMethods lists the sign-in methods it accepts: password,
passwordless-email, passwordless-sms, passkey, and federated. Unset means every method the deployment
enables. A method outside the list is refused with METHOD_NOT_ALLOWED before any credential is examined. See
tenant policy.
Failed attempts and lockouts
Attackers guess passwords and codes at scale, so sign-in, recovery, and MFA flows share persisted rate limits per
account, and optionally per client IP. A refused attempt fails with RATE_LIMITED and a retryAfterMs that tells
the client when to try again. A wrong password for a real, active account is also recorded as auth:signin:fail and
counted in the person's sign-in record. When a real
person locks themselves out, an administrator clears their counters with identities.unlock; when one address
keeps attacking, an incident responder refuses it with security.blockNetwork. See
rate limits and
network restrictions.
Next steps
Sign-up, sign-in, verification, reset, and password rules.
Magic links and codesPasswordless sign-in by email or SMS.
PasskeysPhishing-resistant sign-in that also satisfies MFA.
API keysService accounts and their keys.
Tokens and JWTsSession tokens, session JWTs, JWKS, and web identity.
FederationOAuth, OIDC, SAML, and SCIM.
Better IAM is created by Sean Filimon
Last updated
Authentication
How people and services prove who they are in Better IAM, how a sign-in flows from first factor to session, and where each method is configured.
Email and password
Password sign-up and sign-in, email verification, password reset and change, deployment and organization password rules, and Argon2id hashing in Better IAM.