Authentifizierung

Cliqer authentifiziert über ein httpOnly-Session-Cookie (session_token), das von POST /api/auth/login ausgestellt wird. Browser senden es bei jeder Anfrage automatisch mit; Server-zu-Server-Tools speichern es in einem Cookie-Jar. Teams mit Studio 8K und Enterprise können zusätzlich API-Schlüssel für den Server-zu-Server-Zugriff erstellen, die als Authorization: Bearer cliqer_<key> gesendet werden, siehe API-Schlüssel-Authentifizierung weiter unten.
Sitzungsmodell
| Zugangsdaten | Speicherort | Bezug |
|---|---|---|
Session-Cookie (session_token) | httpOnly- und Secure-Cookie auf Ihrem Cliqer-Host | Gesetzt durch POST /api/auth/login, vom Server automatisch rotiert |
Das Cookie ist HttpOnly und daher nicht aus JavaScript lesbar. Seine Lebensdauer folgt der Sitzungs-TTL des Servers und wird bei jedem Aufruf von GET /api/auth/me transparent verlängert. Es gibt keinen separaten Refresh-Endpunkt: Damit ein langlebiger Client sauber angemeldet bleibt, rufen Sie regelmäßig GET /api/auth/me auf.
Sitzungslebenszyklus
Sitzung verwenden
Browser hängen das Cookie automatisch an. Bei Server-zu-Server-Clients speichern Sie das Cookie, das die Login-Antwort setzt, und senden es bei allen nachfolgenden Anfragen mit:
# Save the session cookie on login, then reuse it
curl -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email":"user@example.com","password":"your_password"}'
curl -X GET "$BASE_URL/api/auth/me" -b cookies.txt
// Same-origin $fetch sends the httpOnly cookie automatically.
// Cross-origin fetch must opt in with credentials: 'include'.
const me = await $fetch('/api/auth/me')
import requests
session = requests.Session() # persists the session cookie
session.post('$BASE_URL/api/auth/login',
json={'email': 'user@example.com', 'password': 'your_password'})
me = session.get('$BASE_URL/api/auth/me').json()
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
// Login stores the session cookie in the jar; later requests reuse it.
client.Post("$BASE_URL/api/auth/login", "application/json", body)
resp, _ := client.Get("$BASE_URL/api/auth/me")
$ch = curl_init('$BASE_URL/api/auth/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'email' => 'user@example.com', 'password' => 'your_password'
]));
curl_exec($ch);
// reqwest with cookie_store persists the session cookie across calls
let client = reqwest::Client::builder()
.cookie_store(true)
.build()?;
client.post("$BASE_URL/api/auth/login")
.json(&serde_json::json!({"email": "user@example.com", "password": "your_password"}))
.send().await?;
let me = client.get("$BASE_URL/api/auth/me").send().await?;
API-Schlüssel-Authentifizierung
Teams mit Studio 8K und Enterprise können langlebige API-Schlüssel für den Server-zu-Server-Zugriff erstellen, als Alternative dazu, die Anmeldedaten eines Benutzers vorzuhalten. Sie erstellen und widerrufen die Schlüssel im Dashboard unter Team → API-Schlüssel (/dash/team/api-keys); zum Erstellen ist der Team-Inhaber erforderlich (oder eine benutzerdefinierte Rolle mit der Berechtigung für API-Schlüssel). Die Schlüsselverwaltung ist ausschließlich in den Plänen Studio 8K und Enterprise verfügbar.


Ein Schlüssel wird bei der Erstellung einmalig angezeigt, im Format cliqer_<prefix>_<secret>. Bewahren Sie ihn sicher auf: Er lässt sich nicht wiederherstellen. Widerrufen Sie ihn daher bei Verlust und erstellen Sie einen neuen. Senden Sie ihn als Bearer-Token an jeden REST-Endpunkt, der eine Sitzung akzeptiert:
curl -X GET "$BASE_URL/api/licenses/status?license_key=YOUR_LICENSE_KEY" \
-H "Authorization: Bearer cliqer_ab12cd34_your_secret_here"
API-Schlüssel funktionieren nur an den REST-Endpunkten. Der WebSocket, einschließlich der Foliensteuerung, benötigt ein Session-Token.
Der Schlüssel authentifiziert sich als der Benutzer, der ihn erstellt hat, beschränkt auf dessen Team und Plan. Ein widerrufener oder unbekannter Schlüssel liefert 401, genau wie eine fehlende Sitzung. Endpunkte, die nur für Admins bestimmt sind, lehnen API-Schlüssel immer mit 403 ab. Für Schlüssel gelten dieselben Rate-Limits wie für Sitzungsanfragen.
Anmelden
Tauscht Benutzer-Anmeldedaten gegen eine Sitzung. Bei Erfolg setzt die Antwort das Cookie session_token.
Endpunkt: POST /api/auth/login
| Parameter | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
email | string | Ja | E-Mail-Adresse des Benutzers (ein WordPress-Benutzername wird ebenfalls akzeptiert) |
password | string | Ja | Passwort des Benutzers |
otp | string | Nein | TOTP-Code, wenn das Konto eine Authenticator-App verwendet |
email_otp | string | Nein | Per E-Mail gesendete PIN, wenn das Konto 2FA per E-Mail verwendet |
curl -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email":"user@example.com","password":"your_password"}'
interface LoginResponse {
data: {
expires: number | null // session lifetime in ms
}
}
const res: LoginResponse = await $fetch('/api/auth/login', {
method: 'POST',
body: { email: 'user@example.com', password: 'your_password' }
})
// The session_token cookie is set by the response; no token is in the body.
import requests
session = requests.Session()
data = {'email': 'user@example.com', 'password': 'your_password'}
res = session.post('$BASE_URL/api/auth/login', json=data)
# The session cookie now lives in `session`.
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
payload := map[string]string{"email": "user@example.com", "password": "your_password"}
body, _ := json.Marshal(payload)
client.Post("$BASE_URL/api/auth/login", "application/json", bytes.NewBuffer(body))
$ch = curl_init('$BASE_URL/api/auth/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'email' => 'user@example.com',
'password' => 'your_password'
]));
$response = curl_exec($ch);
use serde::Serialize;
#[derive(Serialize)]
struct LoginRequest { email: String, password: String }
let client = reqwest::Client::builder().cookie_store(true).build()?;
let response = client
.post("$BASE_URL/api/auth/login")
.json(&LoginRequest { email: "user@example.com".into(), password: "your_password".into() })
.send()
.await?;
Antwort (Erfolg): setzt das Cookie session_token und gibt die Sitzungsmetadaten zurück.
{
"data": {
"expires": 604800000
}
}
Antwort (2FA erforderlich): ein 200-Signal zur Ablaufsteuerung, kein Fehler. Senden Sie POST /api/auth/login erneut mit denselben Anmeldedaten plus otp (Authenticator) oder email_otp (per E-Mail gesendete PIN).
{
"requires_otp": true,
"method": "totp",
"email_sent": false
}
method ist entweder totp, email oder both. Bei email sendet der Server eine PIN per E-Mail, und email_sent gibt an, ob diese E-Mail verschickt wurde. Ein Konto mit both meldet sich mit seinem Authenticator-Code (otp) an; die per E-Mail gesendete PIN ist für dieses Konto kein Anmeldefaktor.
401, ebenso ein falscher oder abgelaufener TOTP-Code. Auch eine falsche oder abgelaufene per E-Mail gesendete PIN liefert 401 mit derselben Meldung wie ein falsches Passwort. Mitglieder einer Organisation, die Single Sign-on vorschreibt, erhalten 403 und müssen sich per SSO anmelden (siehe SSO-FAQ).Sitzungsrotation
Es gibt keinen separaten Refresh-Endpunkt. Der Server rotiert das Cookie session_token für Sie: Steht eine Sitzung kurz vor dem Ablauf, antwortet GET /api/auth/me zusätzlich zum Profil mit einem neuen Set-Cookie. Ein Client, der einen Cookie-Jar führt und regelmäßig GET /api/auth/me aufruft, bleibt ohne weiteren Aufwand unbegrenzt angemeldet.
Aktueller Benutzer
Gibt das Profil des authentifizierten Benutzers zurück oder { "data": null }, wenn keine gültige Sitzung besteht (auch anonym aufrufbar, niemals ein 401).
Endpunkt: GET /api/auth/me
curl -X GET "$BASE_URL/api/auth/me" -b cookies.txt
const { data } = await $fetch('/api/auth/me')
if (data) console.log(data.email, data.is_admin)
me = session.get('$BASE_URL/api/auth/me').json()
resp, _ := client.Get("$BASE_URL/api/auth/me")
$ch = curl_init('$BASE_URL/api/auth/me');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
$response = curl_exec($ch);
let me = client.get("$BASE_URL/api/auth/me").send().await?;
Antwort:
{
"data": {
"id": "uuid",
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe",
"role": "uuid",
"tfa_method": "none",
"is_admin": false
}
}
Abmelden
Beendet die Sitzung für das aktuelle Cookie und entfernt das Cookie aus dem Browser.
Endpunkt: POST /api/auth/logout
curl -X POST "$BASE_URL/api/auth/logout" -b cookies.txt -c cookies.txt
await $fetch('/api/auth/logout', { method: 'POST' })
session.post('$BASE_URL/api/auth/logout')
req, _ := http.NewRequest("POST", "$BASE_URL/api/auth/logout", nil)
client.Do(req)
$ch = curl_init('$BASE_URL/api/auth/logout');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_exec($ch);
client.post("$BASE_URL/api/auth/logout").send().await?;
Antwort:
{
"success": true
}
Bewährte Sicherheitspraktiken
- Ausschließlich HTTPS verwenden: Das Session-Cookie ist
Secure; alle Anfragen müssen TLS 1.2+ verwenden. - Das Cookie niemals aus JavaScript auslesen: Es ist bewusst
HttpOnly, und dabei sollte es bleiben. - Proaktiv erneuern: Rufen Sie in langlebigen Tabs
GET /api/auth/meauf, bevor die Sitzung abläuft; der Server rotiert das Cookie für Sie. - Zum Widerrufen abmelden:
POST /api/auth/logoutbeendet die Sitzung serverseitig sofort. - Anmeldedaten speichern, keine Sitzungen: Server-zu-Server-Integrationen sollten die Anmeldedaten in einem Secret Manager aufbewahren und bei Bedarf eine neue Sitzung aufbauen, das Cookie aber niemals im Quellcode ablegen.