OBS/Kostenpflichtige Module/RESTServer/Zugaenge
- A Preise aktualisieren
- C Personen übertragen
- E Kategorien verwalten
- G Kataloge verwalten
- I Merkliste übertragen
- K Varianten übertragen
- L Artikelvarianten übertragen
- M Referenzarten übertragen
- N Lagerbestände verwalten
- U Bestellungen einlesen
- V leere Passworte füllen
- W Update-Informationen zurücksetzen
- X Konfiguration
- Z Protokoll
Zugänge
Ein Zugang (Account) ist der Kanal, über den ein externer Konsument den REST-Server anspricht. Er bündelt API-Key, Host-Beschränkung, CORS-Konfiguration, optionale JWT-Authentifizierung und optionale mTLS-Subject-Prüfung.
Aufruf
Stammdaten -> Z Weitere Stammdaten -> REST-Server -> Zugänge
Felder
| Feld | Beschreibung |
|---|---|
| Name | Sprechender Name, erscheint im Protokoll und der Statistik |
| Aktiv | Inaktive Zugänge werden bei der Authentifizierung mit 403 abgewiesen |
| API-Key | Eindeutiger Schlüssel, den der Konsument im HTTP-Header apikey senden muss. Per Schlüsselsymbol generierbar |
| Host | Optionaler Hostname oder IP-Adresse des Konsumenten. Stimmt die anfragende IP nicht überein, wird mit 403 abgelehnt |
| CORS-Origins | Liste erlaubter Origins für Browser-Aufrufe, mehrere durch ; getrennt |
| mTLS-Subject | Erwartete RDN-Komponente(n) im Client-Zertifikat-Subject, Komma-/Semikolon-getrennt (alle müssen vorkommen; nur sinnvoll bei mTLS-Servern) |
| JWT | Schalter, ob für diesen Zugang ein JWT-Token nötig ist |
| JWT-Endpunkt | Pfad, über den alle Token-Vorgänge laufen: Anmelden, Erneuern und Abmelden (z.B. oauth oder meineapp/dev/auth). Verglichen wird der vollständige Pfad, mehrstufige Angaben mit / sind also erlaubt |
| JWT-Key | Geheimer Schlüssel zur Signierung/Verifikation, per Schlüsselsymbol generierbar |
| JWT-Exp | Gültigkeitsdauer eines ausgestellten Access-Tokens in Minuten. Derselbe Wert wird dem Konsumenten in der Token-Antwort als expiresIn in Sekunden mitgeteilt und steht dem Endpunkt-Skript als _OBS_JWT_EXP_SEC zur Verfügung |
API-Key
Der API-Key ist die Basis-Authentifizierung. Der Client übergibt ihn im HTTP-Header:
apikey: abcd-efgh-ijkl-mnop-qrstuvwxyz12
Wird ein Aufruf ohne apikey-Header gestellt, sucht der Server intern nach einem Zugang mit API-Key * - damit lassen sich (mit Vorsicht!) auch öffentliche Endpunkte ohne Authentifizierung realisieren.
Host-Beschränkung
Ist im Feld Host ein Wert hinterlegt, akzeptiert der Server Anfragen unter diesem Zugang nur dann, wenn die Remote-IP exakt diesem Host entspricht oder durch DNS-Auflösung auf diese IP zeigt. DNS-Auflösungen werden 5 Minuten gecached.
CORS
Soll ein Endpunkt aus einer Browser-Anwendung (Single-Page-App, JavaScript) heraus aufgerufen werden, muss das Origin der Anwendung in der CORS-Liste des Zugangs eingetragen sein.
Format der Liste:
https://app.kunde.de;https://test.kunde.de;https://localhost:8080
Ablauf:
- Browser sendet einen Preflight-Request (OPTIONS). Der REST-Server prüft das Origin gegen die globale Origin-Liste aller Accounts (Cache, TTL 60s) und antwortet mit 204 + CORS-Header, falls erlaubt.
- Der eigentliche Request wird gegen die Origin-Liste des aktuell verwendeten Zugangs geprüft.
- Stimmt das Origin nicht, wird mit 403 Origin nicht erlaubt für Account ... abgelehnt.
Erlaubte Methoden: GET, POST, PUT, DELETE, PATCH. Erlaubte Header: Content-Type, apikey, Authorization, Accept. Diese Werte sind systemseitig festgelegt und werden in der Preflight-Antwort zurückgegeben.
JWT-Authentifizierung
Wird ein Zugang mit aktiver JWT-Pflicht aufgerufen, sind die folgenden Phasen zu durchlaufen (Phase 3 nur bei genutztem Refresh).
Anmelden, Erneuern und Abmelden laufen über denselben Endpunkt - den im Feld JWT-Endpunkt hinterlegten Pfad. Ein eigener Endpunkt fürs Abmelden ist nicht nötig und wäre auch umständlicher: der JWT-Endpunkt greift vor der Adressauflösung und braucht deshalb weder eine Zeile in RESTSRV_ENDPOINTS noch eine Berechtigung in RESTSRV_ACCESS.
Unterschieden wird nach Verb und Token-Art:
| Aufruf | Ablauf |
|---|---|
| POST ohne Authorization | Anmelden (Skript-Methode Authenticate) |
| POST mit Bearer, Refresh-Token | Erneuern (Skript-Methode Refresh) |
| DELETE mit Bearer, Access-Token | Abmelden (kein Skript nötig) |
| POST mit Bearer, Access-Token | 401 - unverändert |
Absicht. Würde ein vorgelegtes Access-Token beim POST als Abmeldung gedeutet, würde ein Client, der seine beiden Token verwechselt, den Anwender still
abmelden - statt das klare 401 zu bekommen, an dem der Fehler auffällt.Phase 1: Token-Bezug
Der Konsument ruft den JWT-Endpunkt des Zugangs auf, z.B.:
POST https://api.meinserver.de/oauth/ Header: apikey: [API-KEY] Body: {"username":"...", "password":"..."}
Der Server führt das im Zugang hinterlegte Authentifizierungs-Skript aus (F7 in der Zugangs-Liste). Das Skript muss eine Funktion Authenticate bereitstellen, die ein JSON-Objekt mit:
- status = 1 bei Erfolg, 9 bei Misserfolg
- error = Fehlertext (bei status=9)
- _OBS_JWT_ID, _OBS_JWT_SUBJECT, _OBS_JWT_AUDIENCE für die JWT-Claims (optional)
- _OBS_JWT_CLAIM_<name> für beliebige Custom-Claims (z.B. Mandant tenant, Rollen roles); im Folge-Skript lesbar
- _OBS_JWT_REFRESH_ID (optional) löst die Ausstellung eines Refresh-Tokens aus; _OBS_JWT_REFRESH_EXP setzt dessen Lebensdauer in Minuten (Default 90 Tage)
zurückliefert. Bei Erfolg signiert der Server einen Token (HS256, Issuer OBS REST-Server, Lebenszeit gemäß JWT-Exp) und antwortet:
{"token": "eyJhbGciOi...",
"accessToken": "eyJhbGciOi...",
"expiresIn": 28800,
"serverTime": "2026-06-29T15:30:12+02:00"}
Zum Aufbau der Antwort:
- token und accessToken enthalten denselben Token. accessToken ist der Name, den die meisten Client-Bibliotheken erwarten; token bleibt für bestehende Konsumenten erhalten.
- expiresIn ist die Gültigkeit in Sekunden (JWT-Exp × 60).
- Alle weiteren Felder, die das Authenticate-Skript zurückgibt, werden mit ausgeliefert - mit Ausnahme von status und den _OBS_-Steuerfeldern. So kann das Skript z.B. eine Benutzer-Id, den Mandanten oder Rollen direkt in die Anmelde-Antwort legen, ohne dass der Client dafür einen zweiten Aufruf braucht.
Beispiel mit Zusatzfeldern aus dem Skript:
{"benutzerId": "4711", "tenant": "nord", "roles": ["TECHNIKER"],
"token": "eyJ...", "accessToken": "eyJ...", "refreshToken": "eyJ...",
"expiresIn": 28800, "serverTime": "2026-06-29T15:30:12+02:00"}
Mit der Ausstellung legt der Server zu dem Token-Paar eine Sitzungszeile in RESTSRV_TOKEN an. Lässt sich diese Zeile nicht schreiben (Tabelle fehlt, Datenbankproblem), wird kein Token ausgeliefert: der Server antwortet mit 503 und Retry-After. Das ist bewusst von der abgelehnten Anmeldung unterschieden - die Zugangsdaten waren in Ordnung, der Versuch darf wiederholt werden.
Wird die Anmeldung abgelehnt (status = 9), antwortet der Server mit 401 Unauthorized und reicht den Text aus dem Feld error als error.message an den Client durch - der Konsument kann ihn also direkt anzeigen. Die Formulierung liegt damit beim Skript.
Phase 2: Token-Verwendung
Bei allen folgenden Aufrufen sendet der Client den Token im Authorization-Header:
GET https://api.meinserver.de/kalender/v1 Header: apikey: [API-KEY] Authorization: Bearer eyJhbGciOi...
Der Server verifiziert Signatur, Issuer, Ausstellzeitpunkt und Ablauf (Toleranz: 20 Sekunden Clock-Skew) und prüft anschliessend die Sitzung in RESTSRV_TOKEN. Im Skript stehen die Claims als Parameter _OBS_JWT_ID, _OBS_JWT_SUBJECT und _OBS_JWT_AUDIENCE zur Verfügung.
Die Sitzungsprüfung weist ein Token ab, das zu keiner Zeile gehört oder dessen Sitzung gesperrt wurde - beides mit 401 und dem Code AUTH_EXPIRED. Der Grund steht jeweils im Protokoll RESTSRV_PROTO unter TokenStore: ....
wurde, gehört zu keiner Sitzung und wird abgewiesen. Nach dem Update müssen sich
angemeldete Konsumenten also einmalig neu anmelden.Phase 3: Token erneuern (Refresh)
Soll die App lange ohne Neuanmeldung arbeiten, stellt das Authenticate-Skript zusätzlich ein Refresh-Token aus (Feld _OBS_JWT_REFRESH_ID). Zum Erneuern ruft der Client denselben JWT-Endpunkt auf, aber mit dem Refresh-Token im Authorization-Header:
POST https://api.meinserver.de/oauth/ Header: apikey: [API-KEY] Authorization: Bearer eyJhbGciOi... (Refresh-Token)
Der Server erkennt am Bearer-Token den Refresh-Fall, verifiziert das Token (Signatur, Ablauf, token_use=refresh), entwertet es in der Sitzung und ruft erst dann im selben Skript die Methode Refresh auf. Das Skript liefert nur noch die aktuellen Claims zurück - Rotation, Einmalgebrauch und Sperrliste erledigt der Server. Die Antwort ist ein rotiertes Paar {"token","accessToken","refreshToken","expiresIn","serverTime"} in derselben Sitzung.
entfernt werden. Wer sie weiterführt, rotiert zweimal: einmal im Skript, einmal im Server. Der Server sieht die Skript-Tabelle nicht, und das Skript sieht die
Sitzung nicht.Verhalten bei einem bereits benutzten Refresh-Token:
| Lage | Antwort | Wirkung auf die Sitzung |
|---|---|---|
| Erneute Vorlage innerhalb von 60 Sekunden | 401 AUTH_EXPIRED | Keine. Das ist der Normalfall einer App, deren parallele Anfragen gleichzeitig in den Refresh laufen - eine gewinnt, die anderen sollen die vom Gewinner gespeicherten Token benutzen |
| Erneute Vorlage später | 401 AUTH_EXPIRED | Die gesamte Sitzung wird gesperrt. Das Token wurde kopiert oder wiederholt, ohne dass ein Gewinner es noch brauchen könnte; der Vorfall wird mit IP protokolliert |
Details und ein vollständiges Beispiel siehe Scripting.
Phase 4: Abmelden (Logout)
Abgemeldet wird mit einem DELETE auf denselben JWT-Endpunkt, das Access-Token im Authorization-Header:
DELETE https://api.meinserver.de/oauth/ Header: apikey: [API-KEY] Authorization: Bearer eyJhbGciOi... (Access-Token)
HTTP/1.1 204 No Content
Der Server sperrt die ganze Sitzung - also auch die noch nicht abgelaufenen Access-Token aller vorherigen Erneuerungen. Ein Skript ist dafür nicht nötig: es gibt keine Methode Logout, der Vorgang läuft komplett in der Engine. Damit funktioniert das Abmelden für jeden Zugang mit aktiver JWT-Pflicht, ohne dass am Authentifizierungs-Skript etwas geändert wird.
- Ein wiederholter Aufruf ist unschädlich und bleibt 204 - auch wenn die Sitzung längst gesperrt ist.
- Ein abgelaufenes oder ungültiges Token ergibt 401 (es wird schon bei der Prüfung abgewiesen).
- Ein Refresh-Token statt des Access-Tokens ergibt 401.
- Lässt sich die Sitzung nicht sperren, antwortet der Server 503 mit Retry-After - ein Client darf nicht glauben, er sei abgemeldet, während sein Token weiterläuft.
Alle Geräte abmelden
Der Fall „Gerät verloren" ist bewusst nicht am JWT-Endpunkt untergebracht: er braucht eine fachliche Entscheidung darüber, wer das darf. Dafür gibt es das Antwortfeld _OBS_JWT_REVOKE, das jeder gewöhnliche Endpunkt setzen kann:
function Post(oParams: TStrings; oBody: TJSONObject): string;
var oRes: TJSONObject;
begin
oRes := TJSONObject.Create();
try
// 'session' = nur diese Sitzung, 'all' = alle Sitzungen des Subjects
oRes.AddPair('_OBS_JWT_REVOKE' , 'all');
oRes.AddPair('_OBS_HTTP_STATUS', 204);
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
gesetzt hat. Ohne Subject lässt sich die Menge der Sitzungen nicht bestimmen, und
der Aufruf antwortet mit 503.Sitzungen (RESTSRV_TOKEN)
Je ausgestelltem Token-Paar entsteht eine Zeile. Sie enthält die Sitzung, die Zeilenkennung, das Subject, die beiden Laufzeiten, IP und Trace-ID der Ausstellung sowie die Kennzeichen „verbraucht" und „gesperrt".
- Rotation und Widerruf sind getrennt. Ein Refresh entwertet nur das Refresh-Token; der zugehörige Access-Token bleibt bis zu seinem Ablauf gültig. Sonst bekämen parallel laufende Anfragen ein 401, obwohl niemand abgemeldet ist.
- Die Sitzung überlebt die Rotation. Alle Paare einer Anmeldung tragen dieselbe Sitzungskennung - deshalb kann ein Logout sie gemeinsam sperren.
- Aufräumen läuft automatisch beim Serverstart und danach höchstens stündlich. Gelöscht wird nur, was nichts mehr entscheiden kann: Zeilen, deren Access- und Refresh-Laufzeit vorbei sind. Eine verbrauchte Zeile bleibt bis dahin stehen, weil sonst die Erkennung eines später vorgelegten gestohlenen Refresh-Tokens verloren ginge.
- Kein Eingriff nötig. Die Tabelle wird nicht gepflegt; sie ist Diagnose-Material. Alle Abweisungen stehen als TokenStore: ... in RESTSRV_PROTO.
mTLS-Subject-Prüfung
Wird im Feld mTLS-Subject ein Wert hinterlegt, prüft der Server zusätzlich zur Standard-mTLS-Validierung, ob der Subject-DN des (CA-validierten) Client-Zertifikats die hinterlegten RDN-Komponenten enthält. Verglichen wird jede Komponente vollständig (kein Substring-Match) und case-insensitiv; mehrere Komponenten werden durch Komma oder Semikolon getrennt und müssen alle im Subject vorkommen.
Beispiele für Subject-Strings im Cert (OneLine-DN):
/C=DE/O=Kunde GmbH/CN=client-prod.kunde.de /C=DE/O=Kunde GmbH/CN=client-test.kunde.de
Mit CN=client-prod.kunde.de im Feld wird genau das prod-Cert akzeptiert, das test-Cert mit 401 abgewiesen. Da jede RDN-Komponente vollständig verglichen wird, matcht z.B. CN=client1 nicht fälschlich auch CN=client10. Mehrere Bedingungen lassen sich mit Komma/Semikolon kombinieren, z.B. O=Kunde GmbH;CN=client-prod.kunde.de.
Listen-Funktionen
In der Zugänge-Liste (allein):
- Einfg - neuer Zugang
- Return - Zugang bearbeiten
- F4 - Sortierung
- F7 - JWT-Authentifizierungs-Skript bearbeiten
- F8 - Statistik für den Zugang
Aus der Endpunkt-Liste via F6 geöffnet (Auswahl für Berechtigung):
- F2 - markierte Zugänge dem Endpunkt zuordnen
- F5 - Zugang in der Liste markieren / Markierung aufheben