Authentication and session API
Sign-in uses a short-lived challenge, Telegram confirmation and proof of control of the original browser. A Telegram ID supplied by the client is not proof of identity.
On this page
Starting sign-in#
POST /api/auth/telegram/start accepts an empty JSON object {}. It requires the exact application Origin and an available bot. The server creates a challenge lasting 300 seconds and a protected browser-proof cookie.
The 201 response contains challengeId, code, expiresAt and telegramUrl. The proof cookie is excluded from the deep link and is inaccessible to JavaScript. The limit is six starts per minute per hashed address.
const response = await fetch('/api/auth/telegram/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}'
});
const challenge = await response.json();
// Show code and telegramUrl to the user.Waiting for confirmation#
POST /api/auth/telegram/complete accepts {challengeId}. It requires Origin and the original browser-proof cookie. Until confirmation, the response is 202 {status: "pending"}.
After confirmation, 200 returns status=authenticated, user, csrfToken and expiresAt. A session is issued once. Expired challenges return 410; used or cancelled challenges return 409.
const response = await fetch('/api/auth/telegram/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ challengeId: challenge.challengeId })
});
if (response.status === 202) {
// Poll again after a delay; do not create a new challenge.
}Reading the session#
GET /api/auth/me returns 200 {authenticated:false} when no valid session exists. A signed-in response includes the user, CSRF token and expiration.
The __Host-kevmi-session cookie lasts up to 24 hours with Secure, HttpOnly, SameSite=Lax and Path=/. The database stores a SHA-256 hash of its token. A profile form cannot read or replace the cookie.
Account changes#
Mutations require the session, exact Origin and current X-CSRF-Token. Sec-Fetch-Site: cross-site is rejected. The server does not offer arbitrary CORS access.
The account ID comes from the session, not request input. Missing and another user’s records do not disclose distinguishable ownership details.
Signing out#
POST /api/auth/logout ends the current session and clears the cookie. Origin and CSRF are required; the body is not read. Telegram /logout also ends all account sessions.
Cookies do not automatically move to another domain. When changing the service domain, update APP_ORIGIN and the bot webhook, then sign in again.