Sign-in

Sessions and security

Session lifetime, single sign-on, sign-out, brute-force lockouts, bot protection and SMS fraud limits.

View .md llms.txt

#Sessions and single sign-on

When a user signs in, 9in starts a session on the tenant's domain (an http-only cookie). Every application of the tenant shares it: a user signed in for one app isn't asked again by the next (single sign-on), unless that app sends prompt=login or max_age.

  • Lifetime: 30 days by default (Security → Sessions), extended as the user comes back (sliding expiry).
  • Refresh tokens belong to a session: they stop working when it ends.
  • Users see their sessions on the account page and can end any of them, or Sign out everywhere.
  • Admins can end sessions in the console (Users → user, or Sessions) or with the Management API.

#Signing out

WhatHowEffect
Your app onlyDelete your app's tokens and cookieThe 9in session stays: the next sign-in is instant
Your app and 9inRedirect to <issuer>/oauth/logout?id_token_hint=…&post_logout_redirect_uri=… (auth.logout())Ends the 9in session; other apps' refresh tokens from it stop working
EverywhereAccount page → Sign out everywhere, or DELETE /api/v1/users/{id}/sessionsEnds every session of the user

post_logout_redirect_uri must be one of the application's Allowed logout URLs. Without one, the user sees a "You're signed out" page. Unknown or missing URLs never redirect elsewhere.

#Revocation

Signing out, ending a session, blocking or deleting a user, refresh-token reuse and POST /oauth/revoke all take effect on 9in at once: refresh tokens stop working, introspection reports the tokens inactive, and token.revoked / session.revoked webhooks go out. JWT access tokens verified locally keep working until they expire; use hybrid verification to see revocations within seconds (Protecting your API).

Refresh-token rotation: every refresh returns a new refresh token and invalidates the old one. If an old one is ever used again, which only happens when a token was stolen or copied, 9in revokes the whole family of tokens from that sign-in and records token.reuse_detected in the activity log.

#Brute-force protection

  • Accounts: after 10 failed sign-ins (3 to 100), the account is locked for 15 minutes (1 minute to 24 hours). The counter resets after a successful sign-in.
  • IP addresses: locked after ten times as many failures, across accounts.
  • One-time codes: 5 attempts per code (1 to 10), and limits on how many codes one address or IP can request.
  • Locked users see "Too many attempts. Please try again later."

#Bot protection

Security → Bot protection adds Cloudflare Turnstile (invisible for most people, no puzzles) to:

  • sign-up and password recovery (on by default once enabled);
  • sign-in: never, only after failures (default), or always.

9in's own Turnstile widget covers the *.9in.io hostnames. Custom domains need your own widget (site key and secret key), because Turnstile checks the hostname.

#SMS fraud

Text messages cost money, which makes sign-up and code pages a target for "SMS pumping". 9in limits it:

  • Allowed countries (Security → SMS delivery): texts only go to numbers whose calling code you picked. Countries that share a code (+1 for the US, Canada and the Caribbean) are allowed together.
  • Daily cap on texts sent through 9in's account per tenant (per plan), and per-number and per-IP limits on code requests.
  • Refusals are logged (sms.blocked) and shown as console alerts.

#The hosted pages

  • Served only over HTTPS, with X-Frame-Options: DENY and frame-ancestors 'none' (no clickjacking), nosniff, no-referrer, no-store and a strict Content Security Policy: scripts need the page's nonce, and custom themes declare the outside origins they use.
  • Every form has CSRF protection; sign-in transactions expire.
  • Passwords are hashed with argon2id; secrets you store (client secrets, Twilio tokens, signing keys) are encrypted at rest.