Core concept
Sessions
How an end user stays signed in — what the session actually is, how the server validates it, and how it is revoked without a session table.
The session is a signed token, not a database row
There is no session table anywhere in the system.
On signup and login the server issues an HttpOnly cookie:
Set-Cookie: authix_user_session=eyJhbGciOiJIUzI1NiIs…;
Max-Age=86400; Path=/; HttpOnly; SameSite=LaxThe value is a signed JWT. Decoded, it carries:
{
"id": "cmsq…",
"email": "[email protected]",
"role": "user",
"appId": "cmsq…",
"tv": 0,
"iat": 1786553164,
"exp": 1787157964,
"aud": "authix:user"
}Nothing is stored server-side to make this valid. Validity is a cryptographic property of the string itself, which is why sessions cost no storage and no lookup.
What the server checks
Four checks, only one of which touches the database.
| Check | What it catches | Cost |
|---|---|---|
| Signature | Forgery | The token is re-signed with the server's secret and compared. You cannot mint one without it. |
| Audience | Token confusion | End-user and admin tokens use different secrets and a different aud claim, so one is structurally unusable in the other's place. |
| Expiry | Stale sessions | The exp claim, enforced during verification. |
| tokenVersion | Revoked sessions | One indexed read. The tv claim must still match the user's current tokenVersion. |
Revocation without a session store
The tokenVersion column is what makes “sign out everywhere” possible.
Every token embeds the tokenVersion the user had when it was issued. Incrementing that column invalidates every token ever issued for that user, everywhere, on their next request — no list of active sessions to walk, and nothing to clean up.
This happens automatically when
- The user changes their password.
- The user completes a password reset.
Both are moments where a stolen session should stop working, so both bump the version. The device that made the change has to sign in again too — that is the correct trade.
Checking whether someone is signed in
The one call that never throws.
1const session = await authix.checkUserSession();
2
3session.authenticated; // boolean
4session.user; // the signed-in user, when there is oneA missing cookie, an expired token, a revoked session and a network failure all resolve to authenticated: false rather than an error — the server answers with a 200, because “not signed in” is an answer, not a failure. That makes it safe to call on every page load or app launch without a try/catch.
Who gets a session
Only publishable-key callers. This surprises people, so it is worth stating plainly.
A secret key never carries a session
When a request arrives with a secret key, the server does not resolve an end-user session at all — a secret key can target any user directly, so it has no need of one. Calling checkUserSession() on a secret-key client returns authenticated: false even immediately after a successful login on that same client.
That is correct behaviour, not a bug. Use a publishable key when you want the server to scope requests to a signed-in user.
Keeping a session across restarts
Browsers do this for you. Nothing else does.
1# The client holds a requests.Session, so a cookie from login_user()
2# persists for the life of the client.
3authix.login_user(email=email, password=password)
4authix.check_user_session() # acts as that userIn a browser the user agent stores the cookie and replays it automatically, so there is nothing to do. Dart and Python have no such jar, so each SDK provides one — and in Dart it lives in memory, which means you decide where it is written. Treat that value as a bearer token: whoever holds it is that user until it expires.
What the session is not
It decides identity. It does not decide what renders.
Ownership is enforced server-side
A route guard, an auth gate or a middleware check controls what a reader sees. None of them keep one user out of another user's records — the server does that, by pinning every publishable-key request to the session it resolved.
- Client-side guards are user experience. They stop a pointless render.
- The API is the security boundary. It verifies the token on every single request.
- A patched client can render anything and still read nothing it should not.
Related