TinySesam

Sign-in flows

Every way in is its own switch, and they combine. The tag next to each heading is the config that turns it on — nothing here is mandatory, and the whole front end is replaceable.


you do something TinySesam result

Password

password_enabled=True

The classic route. One first factor is enough unless you configure a chain.

  1. identifier + password
  2. /auth/login
  3. session
  4. protected route

The identifier field follows login_identifier: username, email or both.

PIN as a first factor

pin_enabled=True, pin_login=True

Sign in with a short PIN instead of a password — with its own, stricter lockout.

  1. identifier + PIN
  2. /auth/pin
  3. session

With pin_login=False the PIN still exists but is no way in — only an extra factor or a step-up (see below).

Second factor (TOTP)

totp_enabled=True

After the first factor, a one-time code from an authenticator app.

  1. first factor
  2. /auth/totp
  3. 6-digit code
  4. session mfa_ok

Users without 2FA skip the step; recovery codes work too. login_chain=['password','totp'] makes it mandatory — accounts without TOTP are then walked through setup on their next sign-in.

Step-up for sensitive areas

require(mfa=True), stepup_methods=["pin"]

You are signed in — the area still demands a fresh confirmation.

  1. sensitive route
  2. freshness expired?
  3. /auth/reauth
  4. PIN / TOTP / password
  5. area unlocked

stepup_methods names what should confirm. Empty = the strongest method the user has set up (TOTP → PIN → password). A user with none of the listed methods also falls back to their best one — so it is a wish, not a barrier. Add stepup_strict=True for the barrier. Asked again after stepup_max_age_sec.

Factor chain per route

require(factors=["password", "pin"])

A route demands several factors in a fixed order.

  1. password
  2. chain incomplete
  3. /auth/pin
  4. PIN
  5. route unlocked

Someone already signed in only gets the missing field — not the whole login page again. The same works globally via login_chain.

Shared secret (no account)

resource_locks_enabled=True

One area, one PIN or passphrase. No sign-up, no user.

  1. protected area
  2. /auth/resource/<name>
  3. PIN
  4. unlocked, time-limited

Depends(auth.require_resource("guests")) — backed by its own cookie, not by a session.

Sign-in link by email

magiclink_enabled=True + Mailer

Type your address, click the one-time link, you are in.

  1. email
  2. mailer
  3. click link
  4. session

The same mechanism carries invitations, forgot-password and the sign-up email confirmation.

External identity provider

oidc_enabled=True / saml_enabled=True / ldap_enabled=True

OIDC, SAML or LDAP/AD. TinySesam is the client — not the provider.

  1. button
  2. IdP (PocketID, Entra, ADFS …)
  3. callback
  4. session + roles from groups

Map IdP groups onto local roles — after that the same require_role(...) guards apply everywhere.

Forward-auth (other apps)

forward_auth_enabled=True

The reverse proxy asks before every request — this is how you guard apps you did not build.

  1. request to other app
  2. proxy → /auth/forward
  3. 200 + Remote-User
  4. app responds

Without a session: 401 + X-TinySesam-Location → the proxy redirects to login. Caddy/nginx/Traefik examples live in deploy/forward-auth/.


Curious how it feels? The showcase in examples/showcase.py renders these same diagrams — but marks what its own config actually has on.

TinySesam

Login-Flows

Jeder Weg hinein ist ein eigener Schalter, und sie lassen sich kombinieren. Neben jeder Überschrift steht die Config, die ihn einschaltet — nichts davon ist Pflicht, und das ganze Frontend ist austauschbar.


du tust etwas TinySesam Ergebnis

Passwort

password_enabled=True

Der klassische Weg. Ein Erstfaktor genügt, solange keine Kette gesetzt ist.

  1. Kennung + Passwort
  2. /auth/login
  3. Sitzung
  4. geschützte Route

Das Kennungsfeld folgt login_identifier: Benutzername, E-Mail oder beides.

PIN als Erstfaktor

pin_enabled=True, pin_login=True

Statt Passwort mit einer kurzen PIN anmelden — mit eigenem, strengerem Lockout.

  1. Kennung + PIN
  2. /auth/pin
  3. Sitzung

Mit pin_login=False existiert die PIN weiter, ist aber kein Login-Weg mehr — nur noch Zusatzfaktor oder Step-up (siehe unten).

Zweiter Faktor (TOTP)

totp_enabled=True

Nach dem Erstfaktor ein Einmalcode aus der Authenticator-App.

  1. Erstfaktor
  2. /auth/totp
  3. 6-stelliger Code
  4. Sitzung mfa_ok

Wer keine 2FA eingerichtet hat, überspringt den Schritt. Recovery-Codes gehen ebenso. login_chain=['password','totp'] macht ihn zur Pflicht — Konten ohne TOTP werden dann beim nächsten Login zur Einrichtung geführt.

Step-up für sensible Bereiche

require(mfa=True), stepup_methods=["pin"]

Du bist eingeloggt — der Bereich verlangt trotzdem eine frische Bestätigung.

  1. sensible Route
  2. Frische abgelaufen?
  3. /auth/reauth
  4. PIN / TOTP / Passwort
  5. Bereich offen

stepup_methods nennt, womit bestätigt werden soll. Leer = das stärkste Verfahren, das der Nutzer eingerichtet hat (TOTP → PIN → Passwort). Hat er keines der genannten, gilt ebenfalls sein bestes — es ist also ein Wunsch, keine Schranke. Wer die Schranke will, setzt stepup_strict=True dazu. Nach stepup_max_age_sec wird erneut gefragt.

Faktor-Kette pro Route

require(factors=["password", "pin"])

Eine Route verlangt mehrere Faktoren in fester Reihenfolge.

  1. Passwort
  2. Kette unvollständig
  3. /auth/pin
  4. PIN
  5. Route offen

Wer schon eingeloggt ist, bekommt nur das fehlende Feld — nicht noch einmal die ganze Login-Seite. Global geht dasselbe über login_chain.

Geteiltes Geheimnis (ohne Konto)

resource_locks_enabled=True

Ein Bereich, eine PIN oder Passphrase. Keine Registrierung, kein Benutzer.

  1. geschützter Bereich
  2. /auth/resource/<name>
  3. PIN
  4. Bereich offen, zeitlich begrenzt

Depends(auth.require_resource("gaeste")) — hängt an einem eigenen Cookie, nicht an einer Sitzung.

Login-Link per E-Mail

magiclink_enabled=True + Mailer

Adresse eingeben, Einmal-Link anklicken, drin.

  1. E-Mail
  2. Mailer
  3. Link klicken
  4. Sitzung

Derselbe Mechanismus trägt Einladungen, Passwort-vergessen und die E-Mail-Bestätigung bei der Registrierung.

Externer IdProvider

oidc_enabled=True / saml_enabled=True / ldap_enabled=True

OIDC, SAML oder LDAP/AD. TinySesam ist der Client — nicht der Provider.

  1. Knopf
  2. IdP (PocketID, Entra, ADFS …)
  3. Callback
  4. Sitzung + Rollen aus Gruppen

Gruppen des IdP lassen sich auf lokale Rollen mappen — danach greifen überall dieselben require_role(...)-Guards.

Forward-Auth (fremde Apps)

forward_auth_enabled=True

Der Reverse-Proxy fragt vor jedem Request nach — so sichert man Apps, die man nicht baut.

  1. Request an fremde App
  2. Proxy → /auth/forward
  3. 200 + Remote-User
  4. App antwortet

Ohne Sitzung: 401 + X-TinySesam-Location → der Proxy schickt zum Login. Beispiele für Caddy/nginx/Traefik liegen in deploy/forward-auth/.


Wie fühlt sich das an? Das Showcase in examples/showcase.py rendert dieselben Diagramme — markiert dort aber, was seine eigene Config anhat.