Zum Inhalt springen

OBS/Kostenpflichtige Module/RESTServer/Zugaenge: Unterschied zwischen den Versionen

Aus OBS Wiki
Böhrer (Diskussion | Beiträge)
Die Seite wurde neu angelegt: „<br>Sеarch for online reputatiоn management services delhi? I do this ϳob. Ꭺѕ it totallly an underground job It's impossible to use my own site So please…“
 
Rademacker (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
 
(7 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
<br>Sеarch for online reputatiоn management services delhi? I do this ϳob. Ꭺѕ it totallly an underground job It's impossible to use my own site So please place yoᥙr order through email: yangzɦߋu2130@gmaiⅼ.com or turn off goⲟgle image search scrubbіng yanshaiodong@qq.cօm, skype:yarkwinter, phone:+ 86 189-8383-9805. Guaranteed negative reinforcement vs punishment by removaⅼ definition website google ranking factors seo or ѕеnd back your money.<br><br>It has never been еasy tօ deindex a negative review. It's why number of dеfamаtion vісtims have been scamed by reputation company. Actually thiѕ is an unusual wich needs expert working in this sector. My ten years of experiences are your trust for the removal.<br>online reputation management services nationwide retirement ѕolution<br>
{{Kostenpflichtige Module}}
 
=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==
 
{| class="wikitable"
! 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. '''Leer heisst nicht „keine Einschränkung", sondern „kein Browser-Zugriff"''' - siehe unten
|-
| 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.
 
{{Hinweis|Ein Zugang mit API-Key ''*'' sollte nur für bewusst freigegebene Endpunkte verwendet werden und immer in Kombination mit einer Host-Beschränkung oder CORS-Beschränkung betrieben werden.}}
 
==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.
 
Der Preflight kann nicht zugangsbezogen prüfen: der Browser schickt dabei keinen
''apikey'' mit, der Zugang ist zu diesem Zeitpunkt unbekannt. '''Die Durchsetzung
liegt deshalb auf Schritt 2''', nicht auf dem Preflight. Praktische Folge: trägt
'''ein''' Zugang auf dem Server ein ''*'' in seiner Liste, antwortet jeder
Preflight mit ''Access-Control-Allow-Origin: *'' - auch für Zugänge mit engerer
Liste. Deren eigentliche Anfrage wird trotzdem gegen die eigene Liste geprüft.
 
Erlaubte Methoden: ''GET, POST, PUT, DELETE, PATCH'' (systemseitig festgelegt).
 
Bei den '''Anfrage-Headern''' spiegelt der Server im Preflight zurück, was der
Browser in ''Access-Control-Request-Headers'' angefragt hat. Damit sind eigene
Header eines Projekts - ''Idempotency-Key'', ''If-Match'', später
''Upload-Offset''/''Upload-Length'' - ohne Änderung am Server nutzbar. Fragt der
Preflight keine Header an, antwortet der Server mit der Liste
''Content-Type, apikey, Authorization, Accept''.
 
{{Hinweis|Die Header-Liste ist '''keine Schutzgrenze''' - das ist das Origin. Sie
sagt dem Browser nur, welche Header der Server versteht. Dazu kommt, dass die
Zugangsdaten dieser Schnittstelle in Headern stehen (''apikey'',
''Authorization'') und nicht in Cookies: es gibt also keine automatisch
mitgeschickte Berechtigung, die eine fremde Seite ausnutzen könnte.}}
 
===Was „leer" bedeutet===
 
{{Achtung|Ein '''leeres''' Feld CORS-Origins heisst '''nicht''' „keine
Einschränkung". Es heisst: '''jeder Aufruf mit ''Origin''-Header wird mit 403
abgelehnt'''. Für Clients '''ohne''' ''Origin''-Header - mobile Apps, Server,
curl - ändert das Feld gar nichts, sie werden nie gegen CORS geprüft.}}
 
Daraus ergeben sich drei sinnvolle Einstellungen:
 
{| class="wikitable"
! Feld !! Wirkung
|-
| leer || Kein Browser-Zugriff. Richtig für Zugänge, die nur von Apps oder Servern benutzt werden
|-
| konkrete Origins || Browser-Zugriff nur von diesen Seiten. Der Regelfall für Web-Anwendungen
|-
| ''*'' || Browser-Zugriff von überall. Wirkt zusätzlich auf die Preflight-Antwort aller anderen Zugänge - bewusst und sparsam verwenden
|}
 
Für eine Browser-Anwendung ist dieses Feld besonders wichtig, weil ihr API-Key
zwangsläufig im JavaScript liegt und damit öffentlich ist. Die Origin-Liste
verhindert dann, dass jemand den Key nimmt und von seiner eigenen Seite benutzt.
Sie ist allerdings kein Ersatz für eine Zugriffskontrolle: wer den Key hat,
umgeht CORS mit einem einzigen Aufruf ausserhalb des Browsers.
 
==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:
 
{| class="wikitable"
! 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
|}
 
{{Hinweis|Dass das Abmelden am Verb hängt und nicht allein an der Token-Art, ist
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_&lt;name&gt;'' 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.
 
{{Hinweis|Der durchgereichte Text ist für den Endanwender gedacht und sollte
keine internen Details verraten. „Benutzer oder Passwort ist falsch" ist eine gute
Meldung, „Kein Datensatz in BENUTZ mit b_name=meier" nicht.}}
 
===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: ...''.
 
{{Hinweis|Ein Token, das '''vor''' der Einführung dieser Prüfung ausgestellt
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'''.
 
{{Achtung|Eine '''eigene Sperrtabelle im Skript ist nicht mehr nötig''' und sollte
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:
 
{| class="wikitable"
! 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 [[OBS/Kostenpflichtige Module/RESTServer/Scripting|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:
 
<syntaxhighlight lang="pascal" line>
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;
</syntaxhighlight>
 
{{Hinweis|''all'' setzt voraus, dass das Authenticate-Skript ''_OBS_JWT_SUBJECT''
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

Aktuelle Version vom 20. August 2026, 08:30 Uhr

Kostenpflichtige Module

Internet-Shop
UPS
IMS Professional
SMS
Mehrlager-Verwaltung
Mehrsprachen Modul
Multilanguage Modul
EVA Marketing Tool
Termin-Projekte
Edifact-Schnittstelle
Backup Überwachung Email
OBS Geo Daten
DeliSprint / DPD
Filialen
Cashback
Moebelschnittstelle
Dokumenten Manager
DocuWare-Schnittstelle
OFML-Kalkulation
Versicherungsschaden
Gutschriftsanzeigen
Kameraverwaltung
DataInOut
OpenMasterData / IDS
Sammelpositionen



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. Leer heisst nicht „keine Einschränkung", sondern „kein Browser-Zugriff" - siehe unten
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.

HINWEIS: Ein Zugang mit API-Key * sollte nur für bewusst freigegebene Endpunkte verwendet werden und immer in Kombination mit einer Host-Beschränkung oder CORS-Beschränkung betrieben werden.

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:

  1. 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.
  2. Der eigentliche Request wird gegen die Origin-Liste des aktuell verwendeten Zugangs geprüft.
  3. Stimmt das Origin nicht, wird mit 403 Origin nicht erlaubt für Account ... abgelehnt.

Der Preflight kann nicht zugangsbezogen prüfen: der Browser schickt dabei keinen apikey mit, der Zugang ist zu diesem Zeitpunkt unbekannt. Die Durchsetzung liegt deshalb auf Schritt 2, nicht auf dem Preflight. Praktische Folge: trägt ein Zugang auf dem Server ein * in seiner Liste, antwortet jeder Preflight mit Access-Control-Allow-Origin: * - auch für Zugänge mit engerer Liste. Deren eigentliche Anfrage wird trotzdem gegen die eigene Liste geprüft.

Erlaubte Methoden: GET, POST, PUT, DELETE, PATCH (systemseitig festgelegt).

Bei den Anfrage-Headern spiegelt der Server im Preflight zurück, was der Browser in Access-Control-Request-Headers angefragt hat. Damit sind eigene Header eines Projekts - Idempotency-Key, If-Match, später Upload-Offset/Upload-Length - ohne Änderung am Server nutzbar. Fragt der Preflight keine Header an, antwortet der Server mit der Liste Content-Type, apikey, Authorization, Accept.

HINWEIS: Die Header-Liste ist keine Schutzgrenze - das ist das Origin. Sie

sagt dem Browser nur, welche Header der Server versteht. Dazu kommt, dass die Zugangsdaten dieser Schnittstelle in Headern stehen (apikey, Authorization) und nicht in Cookies: es gibt also keine automatisch

mitgeschickte Berechtigung, die eine fremde Seite ausnutzen könnte.

Was „leer" bedeutet

ACHTUNG: Ein leeres Feld CORS-Origins heisst nicht „keine

Einschränkung". Es heisst: jeder Aufruf mit Origin-Header wird mit 403 abgelehnt. Für Clients ohne Origin-Header - mobile Apps, Server,

curl - ändert das Feld gar nichts, sie werden nie gegen CORS geprüft.

Daraus ergeben sich drei sinnvolle Einstellungen:

Feld Wirkung
leer Kein Browser-Zugriff. Richtig für Zugänge, die nur von Apps oder Servern benutzt werden
konkrete Origins Browser-Zugriff nur von diesen Seiten. Der Regelfall für Web-Anwendungen
* Browser-Zugriff von überall. Wirkt zusätzlich auf die Preflight-Antwort aller anderen Zugänge - bewusst und sparsam verwenden

Für eine Browser-Anwendung ist dieses Feld besonders wichtig, weil ihr API-Key zwangsläufig im JavaScript liegt und damit öffentlich ist. Die Origin-Liste verhindert dann, dass jemand den Key nimmt und von seiner eigenen Seite benutzt. Sie ist allerdings kein Ersatz für eine Zugriffskontrolle: wer den Key hat, umgeht CORS mit einem einzigen Aufruf ausserhalb des Browsers.

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
HINWEIS: Dass das Abmelden am Verb hängt und nicht allein an der Token-Art, ist

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.

HINWEIS: {{{1}}}

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: ....

HINWEIS: Ein Token, das vor der Einführung dieser Prüfung ausgestellt

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.

ACHTUNG: Eine eigene Sperrtabelle im Skript ist nicht mehr nötig und sollte

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;
HINWEIS: all setzt voraus, dass das Authenticate-Skript _OBS_JWT_SUBJECT

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