Skip to content

Sign-in & access control

This page covers the dashboard sign-in surface — the methods members use to authenticate, and the controls org owners use to constrain that surface. The API-key surface for installs is separate; see Tenants, projects, keys for that.

Upwarden supports four sign-in methods per member. Owners pick which are enabled at the tenant level; members pick which they enrol in.

The default. The member enters their email, Upwarden sends a one-click sign-in link. The link expires after the per-tenant session duration (see Session management below).

Magic link is always visible on the login page even when other methods are enrolled — the docs and product surface explicitly never hide it, because making it hunt-able would defeat its job as the recovery path. If a member loses their passkey or their TOTP device, magic-link is how they get back in.

Members can sign in via their existing identity-provider account. OAuth providers are enabled per-tenant; the org owner configures which are available.

OAuth sign-in stamps oauth_login_succeeded audit rows with the provider name.

Upwarden’s passkey implementation runs against the WebAuthn standard via the SimpleWebAuthn library — fully Upwarden-owned, no third-party authenticator service involved.

A member registers a passkey from /admin/ui/account/passkeys (TouchID, Windows Hello, a YubiKey, etc.). On subsequent logins they can either click “Sign in with passkey” or — on browsers that support it — see their passkey as an autofill suggestion when they click the email field.

Passkey login produces passkey_authentication_succeeded audit rows with the credential id and a monotonic counter.

For organisations running their own identity provider (Okta, Azure AD, Google Workspace, Auth0, etc.), Upwarden supports both SAML and OIDC. The org owner configures a connection from the tenant settings page; members signing in have their email domain matched against the connection’s claimed-domains list and are bounced through the IdP.

SSO is a per-tenant feature — see the tier matrix for which tiers include it.

Owners can require a TOTP factor (Google Authenticator, 1Password TOTP, Authy, etc.) before sign-in completes. On Team tier an owner can enforce it on themselves; enforcing it on all members begins at Org tier.

The enforcement is tier-driven:

  • Free / Team: optional. Owners can force MFA on themselves but not on members.
  • Org / Enterprise: required for all members. A 14-day grace window applies on upgrades into Org.

A member trying to sign in without an enrolled TOTP factor sees a “Set up MFA” page; once set up, subsequent logins prompt for the TOTP code after the primary auth.

Audit envelopes: mfa_factor_registered, mfa_factor_removed, mfa_required_blocked_sign_in, mfa_grace_expired.

Enterprise-tier tenants can restrict the dashboard surface to a list of CIDR ranges. Configured at /admin/ui/orgs/<slug>/settings/ip-allowlist (owner-only — writes are gated on owner role specifically, because a mis-saved allowlist can lock the org out of its own dashboard).

Empty list = no filtering (default). Non-empty list = requireIpAllowlist middleware 403s any authed request whose first-hop client IP doesn’t match.

Audit envelopes: ip_allowlist_updated (on PUT), ip_allowlist_reject (on 403).

Members can view and revoke their own sessions from /admin/ui/account/sessions. Org owners can force-logout a specific member or every member of the org via the dashboard’s super-admin surface (Enterprise) or the JSON admin API.

Session duration is per-tenant configurable. The tenant default is set via STYTCH_SESSION_DURATION_MINUTES in the operator environment (defaults to 60 min); org owners can override per-tenant via the policy override surface.

Audit envelopes:

EventCarries
member_session_revoked_self{ member_id, session_id } — member revoked their own session
member_sessions_revoked_self_others{ member_id, revoked_count } — member revoked all other sessions but kept the current one
member_force_logged_out_by_admin{ actor_user_id, target_member_id, target_org_slug, revoked_count }
org_all_members_force_logged_out{ actor_user_id, target_org_slug, member_count, revoked_count }