Skip to content

Your account and step-up auth

If you’re a member managing your own sign-in identity — rotating a passkey, signing a stolen laptop out, or swapping the email on your account — this is your surface. It also covers the Confirm it’s you prompt you’ll hit when you try to do something Upwarden considers sensitive, even though you’re already signed in.

The account-scope surface lives under /admin/ui/me/* and is org-agnostic: it shows everywhere you’re a member without forcing you to pick an org first. The IP allowlist that locks down /admin/ui/orgs/* deliberately does not apply here, so an Enterprise tenant can’t accidentally lock you out of your own profile from a coffee-shop network. The step-up gate at /admin/ui/step-up is the secondary control that re-prompts you when an action is dangerous enough to warrant it.

The account landing — /admin/ui/me/profile

Section titled “The account landing — /admin/ui/me/profile”

You arrive here from the account dropdown in the top nav. The page renders five cards plus an optional pending-email banner:

  • Email address — your current sign-in email, with a Change email link to /admin/ui/account/email. If a change is in flight, a yellow banner at the top reads Pending email change to <new@email> — check that inbox to confirm. The verification link expires after 30 minutes. (the minute count tracks STYTCH_EMAIL_VERIFY_EXPIRY_MINUTES).
  • Recovery codes — backup codes you can paste in when your second factor is unavailable. Manage recovery codes takes you to /admin/ui/account/recovery-codes, where the Generate / Regenerate button hands back a one-time list. Plaintext codes are shown once on the success page and never again.
  • Connected providers — the Google / GitHub / Microsoft OAuth identities linked to your account. You can disconnect any one as long as you’d still have at least one sign-in method left.
  • Passkeys — Touch ID, Face ID, Windows Hello, or a hardware security key. Manage passkeys drops you on /admin/ui/account/passkeys.
  • Active sessionsManage sessions on this card sends you to the cross-org sessions view (see below); the legacy single-org view at /admin/ui/account/sessions still works and is what you’ll get from older links.

The dropdown header and the page chrome use /me/* URLs while the per-card action links still point at /admin/ui/account/*. That’s deliberate during the URL-shell rollout — the action endpoints are stable; the chrome moves first.

A thin landing with three link cards: Multi-factor authentication, Recovery codes, Active sessions. Today each card links into the existing /admin/ui/account/* form surfaces; the page exists so the account-scope navigation has somewhere to anchor and so that “where do I rotate my codes” has one obvious answer.

Active sessions — /admin/ui/me/security/sessions

Section titled “Active sessions — /admin/ui/me/security/sessions”

This is the cross-org view: every active Stytch session for your human identity, across every org you belong to. Each row carries an org chip (“Acme Co”), the factor that started the session (magic_link, oauth.google, passkey, totp, …), and three timestamps — started, last active, expires. The session you’re using right now wears a Current badge and its row’s Sign out button is disabled — the tooltip reads This is your current session — sign out from the menu instead.

Two action surfaces:

  • Per-row Sign out — POSTs to /admin/ui/me/security/sessions/<org>/<session-id>/revoke. The org segment lets the handler pick the right Stytch binding from your membership graph.
  • Sign out everywhere except this device — appears at the bottom when at least one non-current session exists. Confirms with Sign out every other browser or device signed in to your account, across every organisation?, then POSTs to /admin/ui/me/security/sessions/revoke-everywhere and writes a single aggregate audit row with scope = 'all_orgs' rather than one row per session.

If one of your orgs’ Stytch regions is slow or down, the page degrades to a partial view rather than 502ing. An error banner at the top reads Couldn't load sessions for: <org names>. — the other rows still render normally.

Lists every passkey registered to your account, with its name and “added on” date. Each row has a Remove button; the button is disabled if removing this passkey would leave you with zero sign-in methods total (counting linked OAuth providers and the always-present Stytch-managed magic-link email). The server enforces the same threshold and returns 412 if you try to bypass the UI.

Register a passkey runs through the SimpleWebAuthn browser library — no third-party authenticator service. The flow:

  1. Browser fetches a creation challenge from /admin/ui/account/passkeys/register/start.
  2. The platform prompts (Touch ID, Windows Hello, hardware key, …).
  3. The credential is POSTed to /admin/ui/account/passkeys/register/complete and you land back on the passkey list with the new row.

If you cancel the platform prompt you’ll see Registration cancelled. inline. If the SimpleWebAuthn bundle fails to load — air-gapped image, CSP block, browser without WebAuthn — the page falls back to a server-rendered “your browser doesn’t support passkeys” response, and you can still register elsewhere and use magic-link from here.

You manage only your own passkeys here. There is deliberately no admin “manage another member’s passkeys” surface — that would defeat the phishing-resistant posture passkeys exist to give you. Admins who need to force a user out use /admin/ui/orgs/<slug>/members/<id>/force-logout instead.

You’ll see this page when you try to do something sensitive while signed in — the route handler 303s you here, you confirm your identity, and we 303 you straight back to where you came from. The page header reads Confirm it’s you with the lead This action is sensitive enough that we need to re-confirm your identity, even though you're already signed in. Re-authenticate to continue.

Today the gate sits on five POST endpoints (#411, expanding via the per-tenant registry in #626):

  • Generate / regenerate recovery codes
  • Remove a passkey
  • Remove a TOTP factor
  • Change email
  • Disconnect an OAuth provider

It does not fire on a GET — browsing to /admin/ui/me/passkeys won’t prompt; clicking Remove will.

Today the only wired factor is password — you type the admin token and submit. The label on the form is Admin token; the submit button reads Confirm. The page is structurally ready for passkey and MFA step-up (#404 and #407), and you’ll see grey placeholder notes when those factors aren’t yet wired on your deployment:

  • Passkey step-up is coming with the #404 rollout. Use the admin-token form for now.
  • MFA step-up is coming with the #407 rollout. Use the admin-token form for now.

A wrong token surfaces Incorrect token — try again. and a 401 (which monitoring picks up as a failed-step-up signal separate from failed-sign-in attempts).

A successful step-up writes a vg_step_up cookie that’s good for 5 minutes by default. The formnote on the page tells you the same thing: This re-auth lasts a few minutes. You won't be re-prompted again until it expires — but every sensitive op resets that window. The cookie is HMAC-pinned to your member id, so copying it to another browser won’t work — and it’s SameSite=Strict, so it never rides a cross-site navigation.

The page carries the original URL in a hidden next field, validated against the same safe-redirect regex the login surface uses. On success you 303 back to the sensitive op. On a session that’s since expired, the step-up page’s own session gate bounces you to /admin/ui/login and the post-login next-carry sends you back to the original op — you don’t lose your place.