Mobile- und Native-App: Login & Sync
Zielgruppe: Entwickler einer Mobile-App (Flutter, Swift, Kotlin, React Native, Tauri-Desktop, …) oder eines Drittanbieter-Clients, der mit Prilog reden soll.
Status: Spezifikation gültig ab 2026-05-12 (JWT-only Login-Architektur).
Architektur-Überblick
Eine Prilog-Native-App spricht mit zwei Hosts:
https://api.prilog.chat— Plattform-API (Login, Bootstrap, Berechtigungen, Modul-Daten).https://<tenant>.prilog.team— Matrix-Server für Realtime-Chat (Sync, Send, Media, Encryption).
Du brauchst zwei Tokens:
| Token | Wo wird er benutzt? | Lebenszeit |
|---|---|---|
prilog_jwt | Alle /api/*-Aufrufe (Bootstrap, Module) | 24h |
matrix_access_token | Alle /_matrix/*-Aufrufe (matrix-js-sdk) | bis Logout |
Beide bekommst du aus einem einzigen POST /api/auth/v1/login-Call. Du musst NICHT separat gegen Synapse einloggen — externe /_matrix/client/v3/login wird mit 403 abgewiesen.
Login (initial)
POST https://api.prilog.chat/api/auth/v1/login
Content-Type: application/json
{
"tenant": "demo", // entweder slug oder volle matrix-domain
"username": "anna", // local-part, ohne @ und ohne :domain
"password": "<User-Passwort>",
"deviceName": "Prilog iOS — Anna's iPhone", // wird auf Synapse als display_name
"issueRefreshToken": true // empfohlen: true bei /login
}Antwort 200:
{
"token": "eyJhbGciOiJIUzI1NiI...", // prilog_jwt
"expiresIn": 86400, // Sekunden
"matrixAccessToken": "syt_YW5uYQ_abcdef...", // matrix_access_token
"matrixUserId": "@anna:demo.prilog.team",
"homeserver": "demo.prilog.team",
"refreshToken": "9hQyD...", // optional
"refreshTokenExpiresAt": "2026-06-11T20:48:00.000Z"
}Antwort 401 (User unbekannt, Passwort falsch, oder Konto deaktiviert):
Antwort 429 (Konto temporär gesperrt nach 10 Fehlversuchen — 15min Lock):
Antwort 503 (Synapse nicht erreichbar — Retry später):
{ "code": "INVALID_MATRIX_TOKEN", "message": "...", "retryable": false }Token-Storage (App-Seite)
Speichere sicher (iOS Keychain, Android KeyStore, Tauri Secure Storage):
prilog_jwt+expiresIn(für Auto-Refresh-Logik)matrix_access_token(Synapse-Token, kein expiresIn — bis explizites Logout gültig)matrix_user_idhomeserverrefresh_token+refreshTokenExpiresAt
Refresh
Ein prilog_jwt läuft nach 24h ab. Statt User erneut nach Passwort zu fragen, nutze den Refresh-Token:
POST https://api.prilog.chat/api/auth/v1/refresh
Content-Type: application/json
{ "refresh_token": "9hQyD..." }Antwort 200:
{
"token": "eyJ...", // neuer prilog_jwt
"expiresIn": 86400,
"refreshToken": "...", // neuer refresh_token (Rotation)
"refreshTokenExpiresAt": "..."
}Wichtig: Refresh-Tokens werden bei jeder Nutzung rotiert. Wenn ein bereits genutzter Token nochmal kommt, wird das Token-Family revoked (Replay-Attack-Schutz). Die App muss den neuen Token immer überschreiben.
Bei Refresh-Fehler (401, REFRESH_TOKEN_REUSED, etc.): User zurück zur Login-Page.
Matrix-Token braucht KEINEN Refresh — er bleibt gültig bis explizites Logout.
Logout
POST https://api.prilog.chat/api/auth/v1/logout
Content-Type: application/json
{ "refresh_token": "..." } // revokes refresh-token, optional→ 204 No Content. Lokale Tokens löschen, optional Synapse-/_matrix/client/v3/logout aufrufen (mit matrix_access_token).
Passwort ändern
POST https://api.prilog.chat/api/auth/v1/change-password
Authorization: Bearer <prilog_jwt>
Content-Type: application/json
{ "currentPassword": "alt", "newPassword": "neu-12345" }→ 204 (Erfolg), 401 (currentPassword falsch), 429 (Locked), 400 (newPassword < 8 chars). Existierende Sessions bleiben gültig (Matrix-Tokens werden nicht invalidiert).
Matrix-Sync (Realtime-Chat)
Mit dem matrix_access_token sprichst du direkt mit Synapse:
GET https://demo.prilog.team/_matrix/client/v3/sync?since=...&timeout=30000
Authorization: Bearer <matrix_access_token>Empfohlen: matrix-js-sdk (Web/React-Native) oder die offiziellen SDKs (Element-Android-SDK, MatrixSDK.swift, etc.). Setup-Beispiel mit matrix-js-sdk:
import { createClient } from 'matrix-js-sdk';
const client = createClient({
baseUrl: `https://${homeserver}`, // demo.prilog.team
accessToken: matrixAccessToken,
userId: matrixUserId,
deviceId: undefined, // optional
});
await client.startClient({ initialSyncLimit: 30 });Verfügbare Matrix-Endpoints unter https://<homeserver>/_matrix/*:
/_matrix/client/v3/sync(long-polling)/_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}(Send)/_matrix/media/v3/upload(Media)/_matrix/client/v3/rooms/{roomId}/state(Read State)
Geblockt (HTTP 403 mit M_FORBIDDEN):
POST /_matrix/client/v3/login→ Use/api/auth/v1/logininsteadPOST /_matrix/client/r0/login(gleicher Block, alle Versionen)
Plattform-API (Bootstrap, Module)
Alle Prilog-spezifischen Daten kommen via prilog_jwt:
GET https://api.prilog.chat/api/platform/v1/bootstrap
Authorization: Bearer <prilog_jwt>Antwort enthält: Spaces, Aufgaben, Module, Berechtigungen, Profil. Detail-Endpoints unter /api/platform/v1/* und /api/customer/* (für Schul-Admin) — siehe Platform API Referenz.
Push-Notifications
Mobile-Apps können Synapse-Push-Gateways nutzen (Standard-Matrix-Spec):
POST https://demo.prilog.team/_matrix/client/v3/pushers/set
Authorization: Bearer <matrix_access_token>Body folgt der Matrix-Pusher-Spec (FCM/APNs Push-Gateway-Config).
Tenant-Discovery
Wenn der User nur die Matrix-ID kennt (@anna:demo.prilog.team) und keine Tenant-URL, kann die App .well-known-Lookup machen:
GET https://demo.prilog.team/.well-known/matrix/client→ { "m.homeserver": { "base_url": "https://demo.prilog.team/" } }
Für Prilog-Login: extrahiere tenant aus dem Domain-Teil und nutze https://api.prilog.chat/api/auth/v1/login mit { tenant: "demo.prilog.team", username: "anna", ... }.
Mobile-spezifische Fallstricke
- iOS Safari / WKWebView lokale Files:
File-Refs verlieren Bytes wenn der Picker zwischenzeitlich schließt. Lies Files sofort in einenArrayBuffer. - Refresh-Token-Race: Wenn die App zwei parallele Refresh-Aufrufe macht (z.B. Sync und Bootstrap gleichzeitig), wird der zweite revokes. Lösung: in der App Refresh serialisieren oder Mutex.
device_idist optional im aktuellen Backend-Flow — wenn du End-to-End-Encryption nutzen willst, brauchst du jedoch eine stabiledevice_id. Setze sie explizit beim ersten Login per Custom-Endpoint (Backlog) oder nutze den Matrix-Standard-POST /devices.- Rate-Limiting: 10 Fehlversuche → Konto 15min gesperrt. Zeige dem User Countdown statt nur "401".
Beispiel-Sequenz (Pseudo-Code)
// 1. Initial-Login
const r = await fetch('https://api.prilog.chat/api/auth/v1/login', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tenant, username, password, deviceName: 'My App', issueRefreshToken: true }),
});
if (r.status === 401) showLoginError();
if (r.status === 429) showLockedError();
const { token, matrixAccessToken, matrixUserId, homeserver, refreshToken } = await r.json();
await SecureStore.set({ token, matrixAccessToken, matrixUserId, homeserver, refreshToken });
// 2. Bootstrap mit Prilog-JWT
const bootstrap = await fetch('https://api.prilog.chat/api/platform/v1/bootstrap', {
headers: { Authorization: `Bearer ${token}` },
}).then(r => r.json());
// 3. Matrix-Sync starten
const matrixClient = createClient({ baseUrl: `https://${homeserver}`, accessToken: matrixAccessToken, userId: matrixUserId });
await matrixClient.startClient();
// 4. Token-Refresh wenn prilog_jwt nahe abgelaufen
async function refreshIfNeeded() {
if (Date.now() > expiresAt - 60_000) {
const r = await fetch('https://api.prilog.chat/api/auth/v1/refresh', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken }),
});
if (!r.ok) logout();
const next = await r.json();
await SecureStore.set({ token: next.token, refreshToken: next.refreshToken });
}
}CORS / TLS
api.prilog.chatist CORS-frei für*.prilog.teamund für Capacitor/iOS/Android-Schemes.- Native-Apps brauchen keine spezielle Origin-Header-Konfiguration.
- TLS-Pinning ist optional. Wer es nutzen will:
api.prilog.chatläuft auf Let's-Encrypt-Standard, Cert dreht alle 90 Tage.
Migration: vorhandene Matrix-Clients aktualisieren
Wenn deine App bisher direkt POST /_matrix/client/v3/login aufruft (= alter Web-Client-Flow), funktioniert das nicht mehr seit 2026-05-12 — nginx blockt mit 403.
Umstellen:
| Alt | Neu |
|---|---|
POST <homeserver>/_matrix/client/v3/login | POST api.prilog.chat/api/auth/v1/login |
Antwort: { access_token, user_id, device_id } | Antwort: { matrixAccessToken, matrixUserId, token } |
POST <homeserver>/_matrix/client/v3/account/password | POST api.prilog.chat/api/auth/v1/change-password |
Alle anderen /_matrix/*-Aufrufe (sync, send, media) bleiben unverändert.
Support & Issues
- Backend: prilog-backend-api Repo
- Connector: prilog-matrix-connector Repo
- Bugs: GitHub Issues oder support@prilog.chat