Skip to content

Sessions and Sign in

Dejan Strbac edited this page Nov 12, 2026 · 1 revision

This page explains, from the operator's side, how a user signs in to ALPS and how long they stay signed in. It covers the in-memory session, its idle and absolute limits, the session caps, login_key and the login token, the "Stay signed in after closing the browser" box, what a restart does, and linked accounts with account switching. For full detail, see docs/SESSIONS.md. The keys are listed on Config-Server.

Signing in

ALPS has no user database. The sign-in form sends the username and password to POST /session, and ALPS checks them with the mail provider: the imap provider signs in to the IMAP server with them, and the maildir provider looks them up in its passwd file. A multi provider uses one of these for each login (see Mail-Providers). If the password is accepted, the user is in. The same username and password are used later for SMTP submission and the ManageSieve plugin, and for the CalDAV and CardDAV servers unless the user saves other credentials for them in Settings (see Mail-Server-Requirements).

The sign-in answers:

Answer Meaning
200 {"ok":true} Signed in. The session cookies are set.
200 {"requires_2fa":true} The password is right and the account has a security key. The key step follows (below).
400 Missing credentials The username or the password was empty. When both are empty, ALPS first tries the browser's login token, and answers this only when there is no usable one (see Restoring a session).
401 Failed to login The mail server refused the username or password.
429 with Retry-After (whole seconds, rounded up) A login rate limit was reached (see Config-Rate-Limiting-and-WebAuthn and docs/RATELIMIT.md).
503 connection_limit The password is right, but the account already holds as many connections as the mail server allows.
503 service_unavailable The mail server could not be reached or could not check the password, or it asked for a protected connection (see Mail-Server-Requirements).

Every attempt is logged, whatever [logging] access says, at info or warn level (so [logging] level = "warn" keeps only the failures): sign-in on success, and sign-in failed with a reason (bad credentials, rate limited, connection limit, error, second factor refused or recovery code refused). A sign-in that waits for a security key is logged as sign-in awaits second factor. With bad credentials, the line's error field says why the login was refused: the mail server's answer, for example, or under the multi provider no mail provider configured for domain ... when no rule covers the login's domain. The password is never logged.

Accounts with a security key

When the account has a security key, the password step opens a pending session and sets the alps_2fa_pending cookie for 5 minutes. The browser then asks for the key, or for one of the account's recovery codes. The full session is issued only after that step. If the 5 minutes pass, or the pending session is gone (for example after a restart), the page returns to the password form. The pending session exists only on the node that took the password, so in a cluster the key step must reach that node. Configuration and recovery are covered in Config-Rate-Limiting-and-WebAuthn, Security-Features and docs/WEBAUTHN.md.

What a session is

A session lives in the memory of the ALPS process that created it. It holds:

  • its own connection to the mail server, opened at sign-in and opened again if the server drops it;
  • a second, background connection for work the user is not waiting on, such as checks for new mail and draft saves. It is opened when first needed and closed after a minute unused, and it is not used with background_connection = false;
  • its own cache of message lists and message structure, and the attachments being uploaded for its drafts.

No SMTP connection is kept open. Each send opens a new one.

Sessions are not written anywhere. When the process stops, they are gone, and nodes in a cluster do not share them. Each session normally holds up to two connections to the mail server (for a moment a third, while a command it gave up on finishes), so a user's sessions together can hold about twice max_sessions_per_user. Check that against the mail server's per-user connection limit (see Mail-Server-Requirements).

Cookies

Cookie What it holds Page can read it Lifetime
alps_session A random token naming the in-memory session No (HttpOnly) 30 days if the box was ticked, otherwise until the browser closes
alps_logged_in 1, so the page knows a session was issued Yes Same as alps_session
alps_login_token The encrypted login token (only with login_key) No (HttpOnly) 30 days if the box was ticked, otherwise until the browser closes
alps_has_login_token 1, telling the page that a login token was issued Yes Same as alps_login_token
alps_2fa_pending The token of a sign-in or account switch waiting for its security key No (HttpOnly) 5 minutes

alps_has_login_token is set only together with the token itself. Without login_key, or when the token cannot be encrypted, a sign-in clears both cookies instead.

Every cookie has Path=/ and SameSite=Strict. ALPS adds Secure when the browser's connection is HTTPS. That is the case when ALPS terminates TLS itself, or when the request comes from an address listed in [server] trusted_proxies with X-Forwarded-Proto: https. Behind a TLS-terminating proxy that is not listed, the cookies go out without Secure. List only your own proxies in trusted_proxies (see Setup-Behind-a-Reverse-Proxy).

When a session ends

The idle window

A session ends when it has received no authenticated request for the length of its idle window. Every authenticated request resets the window, including the ones an open page sends on its own, such as checks for new mail.

The window is the user's Auto-logout choice in Settings: Never, 15, 30, 60, 120 or 360 minutes. It is read when the session opens. Saving a new choice applies at once to the session it was saved from. The account's other sessions keep the window they opened with.

  • max_session_minutes (default 0, no cap) caps the chosen time for the server's idle window. "Never" becomes max_session_minutes, or one year when no cap is set.
  • session_minutes (default 30) is the window of an account that has not chosen an Auto-logout time.

For such an account, Settings shows session_minutes as the Auto-logout choice, and lists it as an extra entry, such as "45 minutes", when it is not one of the times above. The page signs out after that many idle minutes, and the server ends the session after them. Saving other settings does not store the time for the account: only a choice the user makes under Auto-logout does, and from then on that choice applies instead. A change to session_minutes therefore applies to every account that has not chosen a time.

The page keeps its own idle timer, shared by the browser's tabs. It counts keys, pointer, touch and scrolling, including inside message frames. While the user is active, it calls GET /session at most every five minutes. Once the user has been idle for the Auto-logout time, it saves the open drafts and signs out with DELETE /session. With "Never", the page does neither.

The absolute limit

absolute_session_hours (default 168, which is 7 days) ends a session however much it is used, including under "Never". It is never shorter than session_minutes. 0 or unset keeps the default, and a negative value such as -1 turns the limit off.

The session caps

max_sessions (default 10000, per node) and max_sessions_per_user (default 10, per node) cap how many sessions a node holds. When a new session would go over a cap, the least recently used session (of all users, or of that user) is ended. A sign-in is never refused for this. A value of 0 keeps the default, and a negative value, such as -1, turns that cap off.

With login_key set, the browser whose session was ended this way is not signed out while its login token is valid. Its next request opens a new session from the token, which may in turn end another session of the same user. Set max_sessions_per_user above the number of browsers and devices a user normally has open at once, or sessions end and reopen in turn, and each reopening signs in to the mail server again.

Other ways a session ends

  • Sign Out, and the page's auto-logout, send DELETE /session. It closes the session and clears alps_session, alps_logged_in, alps_login_token and alps_has_login_token.
  • A password the mail server refuses, at any request, closes the session and clears the same four cookies, and the request gets 401. This is what happens when the password was changed outside ALPS and the session needs a new connection.
  • A mail server that cannot be reached, or that will not open another connection, ends the in-memory session but leaves the cookies. The request gets 503 {"error":"mail_server_unavailable"}. With a login token, a request made once the server is back opens a new session.
  • An account switch closes the session it switches from (see Linked accounts).

What the log says

When a session that was issued to a browser ends, ALPS logs session ended with a reason:

reason Cause
idle The idle window passed
max-age absolute_session_hours was reached
evicted A session cap made room for a new session
sign-out Sign Out or the page's auto-logout
switched The user switched to a linked account
auth-error The mail server refused the password, also when the session opened its mail connection again, for example after the password was changed outside ALPS
mail-server-unavailable The mail connection could not be opened again for any other reason

Sessions closed at shutdown are not logged. A session opened from a login token is logged as session restored. A restore that fails because the mail server is unavailable is logged as session not restored at warn level.

login_key and the login token

Without login_key, a session lasts only as long as the process that holds it. Every restart signs everyone out, account linking is refused, and the CalDAV and CardDAV connections in Settings can use only the mail username and password.

With login_key set in [server], every successful sign-in also writes the alps_login_token cookie. This happens whether or not the box was ticked; the box decides only the cookie's expiry. The token holds the sign-in's credentials and choices, encrypted and signed with login_key, so the browser cannot read or change it. For an account with a security key, the token is written only after the key or a recovery code was given.

The key is 32 random bytes, written as standard or URL-safe base64, or as 64 hex characters:

openssl rand -base64 32
[server]
login_key = "paste-the-generated-value-here"

A value that does not decode to 32 bytes stops ALPS at startup with Error generating options: invalid login key.

Keep the key secret, like a password. It decrypts every login token, every stored linked-account password, and every separate CalDAV and CardDAV password users save in Settings. Keep the configuration file readable only by the user ALPS runs as. In a cluster, every node that serves the same hostname needs the same key.

Restoring a session

When a browser sends an alps_session cookie that names no session in memory (after a restart, or after the session reached a limit), ALPS reads the login token:

Outcome What happens
Token good, mail server accepts the password A new session opens. New alps_session and alps_logged_in cookies are issued with the same expiry choice as before, and the request goes on. The token itself is not rewritten.
Account has a security key and the token does not record it as given The request gets 401, and the user signs in again with password and key.
Mail server refuses the password All four session and token cookies are cleared, and the request gets 401.
Mail server unavailable, or the session cannot be opened for another reason Every cookie is kept, and the request gets 503 mail_server_unavailable. The next request tries again.
No token, an unreadable one, or one older than 30 days The session cookies and any unreadable token cookie are cleared, and the request gets 401.

How long a token lasts

ALPS accepts a login token for 30 days after it was written, whether or not the box was ticked. Restoring a session does not extend that. A new token is written only by:

  • a sign-in;
  • giving the security key or a recovery code at sign-in;
  • adding a security key, when the browser already holds a valid token;
  • a password change through the password plugin (see Setup-Password-Change);
  • a switch to a linked account.

After 30 days, once the in-memory session ends, the user signs in again.

Changing the key

Changing or rotating login_key makes every login token unreadable, so every user signs in again. It also makes every stored linked-account password unreadable, so switching to a linked account fails until the user removes that account in Settings and adds it again. Separate CalDAV and CardDAV passwords saved in Settings become unreadable too: those connections fail, without falling back to the mail password, until the user enters the password again. Use a key change on purpose, for example to end every kept sign-in at once.

"Stay signed in after closing the browser"

The box on the sign-in page is off unless the user ticks it. It decides only whether the session cookies outlive the browser:

  • Ticked: alps_session, alps_login_token, alps_logged_in and alps_has_login_token carry a 30-day expiry.
  • Unticked: they are browser-session cookies, and the browser drops them when it closes.

The choice is kept in the login token. While the token is valid, a restored session and a switch to a linked account issue their cookies the same way as the original sign-in did. A switch made after the token has expired issues browser-session cookies. A sign-in that waits for a security key keeps the choice with its pending session.

The box does not override Auto-logout. With an auto-logout time set, an idle open tab still signs itself out, which clears all four cookies. The page's timer runs only while a tab is open.

How long a user stays signed in depends on login_key:

Situation Without login_key With login_key
Tab left open and idle The page signs out after the Auto-logout time. With "Never", the session runs until a limit or a restart ends it, and then the user signs in again. The page signs out after the Auto-logout time. With "Never", a session that ends at a limit or a restart is opened again from the token, until the token is 30 days old.
Tab closed, browser still open A visit within the idle window finds the session. After the idle window, the user signs in again. A visit within the idle window finds the session. A later visit opens a new session from the token, until the token is 30 days old.
Browser closed, box unticked The cookies are gone, and the user signs in again. The cookies are gone, and the user signs in again.
Browser closed, box ticked The cookies are kept, but the session still ends after its idle window. After that, the user signs in again. A visit within the idle window finds the session. A later visit opens a new session from the token, until the token is 30 days old.

The box only does what its label says when login_key is set.

Restarts and upgrades

ALPS stops on SIGINT or SIGTERM. It gives requests in flight up to 30 seconds to finish, then ends every session and waits up to 5 more seconds for the sessions to release their mail connections and uploads. SIGHUP does not stop ALPS: from the moment ALPS has opened its log, early in startup and before it joins a cluster or checks the plugins' servers, SIGHUP reopens the log file, if [logging] output names one, and sessions are kept.

After a restart, all sessions are gone:

  • With login_key, the browser's next request opens a new session from its login token, if the token is still valid. The user stays signed in.
  • Without login_key, the next request gets 401, and the user signs in again.
  • In either case, attachments that were uploaded but not yet saved into a draft or sent are lost. The composer lists them as files to add again at its next save or send. A sign-in waiting for its security key starts over.

Restarting ALPS does not reset the 30-day limit of the login tokens, and it does not change the stored linked accounts. For production checks, see Running-in-Production.

Several nodes

Sessions are local to the node that created them. Behind a load balancer:

  • Route each browser to the same node every time (session affinity). A sign-in waiting for its security key exists only on the node that took the password.
  • Give every node the same login_key. A browser that reaches another node after a failover then gets a new session from its login token. Without the same key, it is signed out.
  • ALPS issues a new alps_session value at sign-in, at a restore and at an account switch. Affinity keyed on that cookie's value has to follow those changes. An affinity cookie set by the load balancer, or source-IP affinity, avoids that.

See Clustering, Config-Cluster, Setup-Cluster and docs/CLUSTER.md.

Linked accounts

Users can link other mail accounts they hold the passwords for, and switch between them from the account menu without typing a password again. Linking needs [server] login_key. Without it, adding an account answers Account linking requires login key to be configured.

Adding and removing a link

  • Adding an account signs in to it with the password given. The attempt counts against the login rate limits for that account and for the client's IP address, like a sign-in at the form.
  • A link is stored in both accounts. Each account keeps the other account's username and password, encrypted with login_key, in its ALPS data: in IMAP METADATA (see Storage-in-IMAP-METADATA), or in alps_store.json with the maildir provider. A mail server without METADATA keeps nothing ALPS saves, so links do not last there.
  • Adding an account that is already linked is refused with This account is already linked. An account cannot be linked to itself.
  • Removing a link removes it from both accounts. If the other account cannot be updated, for example after a login_key change, the link is still removed from this account. To clear the other side as well, add the account again and then remove it.
  • A password change through the password plugin rewrites the copy stored in every linked account. If some could not be updated, Settings says "Your password has been changed. Some linked accounts could not be updated."

With the maildir provider, an account whose maildir does not exist yet can still be linked: the first save creates the maildir.

Switching accounts

A session holds the mail connection of one account, so a switch opens a new session:

  1. ALPS reads the target's stored password from the current account and signs in to the target.

  2. If that fails, the current session stays open, and the menu says why:

    Cause Answer The menu says
    The target's mail server refused the stored password (changed outside ALPS), or the stored password cannot be read (after a login_key change) 409 relink_required "Remove this linked account in Settings, then link it again with its current password."
    The target's mail server could not be reached 503 service_unavailable "The other account is temporarily unavailable. Please try again later."
    The account is no longer linked 404 account_not_found "This account is no longer linked. Refresh the account list."

    The server log names the cause on its Switching linked account: ... line. A failure to read the current account's own links is handled like any other request of that session: a refused password signs it out, and an unreachable mail server answers 503 mail_server_unavailable.

  3. If the target has a security key, the switch asks for that key, or for one of the target's recovery codes. The current session is closed at once, and the target's session is issued only once its key is given within 5 minutes. Leaving the key page goes back to the sign-in page, not to the previous account. ALPS logs account switch awaits second factor, and then sign-in once the key is given.

  4. The key is not asked for when the target account has turned on Trust Linked Accounts in its security-key settings, and the current session has given its second factor: its own key or recovery code, or a trusted switch (see Security-Features).

  5. The previous session is closed (session ended with reason switched), and the new account's cookies follow the "Stay signed in" choice held in the browser's login token. A switch that needs no key is logged as account switch.

Afterwards, ALPS holds mail connections for that browser only for the account it switched to. Linking, unlinking and a password change sign in to the other accounts briefly. While those sessions are open, they count against max_sessions and max_sessions_per_user.

When an account moves to another IMAP server, its links keep working only if the migration copies ALPS's METADATA entries, the new server supports METADATA, and login_key stays the same. Otherwise, users remove their linked accounts and add them again. Saved CalDAV and CardDAV passwords need the same login_key too, or users enter them again.

Checklist

  • Set login_key, keep it secret, and use the same value on every node.
  • Serve ALPS over HTTPS, either directly or behind a proxy listed in trusted_proxies that sends X-Forwarded-Proto, so that the cookies carry Secure.
  • Keep [server] debug off in production (see Config-Logging-and-Cache).
  • Keep absolute_session_hours set. The default is 7 days.
  • Size max_sessions_per_user for the devices users have, and leave room for two connections per session in the mail server's per-user connection limit.
  • In a cluster, use session affinity.
  • Watch the log for sign-in failed, session not restored and session ended.

Clone this wiki locally