Lokaler Mock-Server

keenauth mock serve startet auf Ihrem Rechner einen kleinen OpenID-Connect-, SAML-2.0- und SCIM-2.0-Server sowie eine In-Memory-Version der /v1-Management-API, für CI-Jobs ohne Netzwerkzugang und für die lokale Entwicklung. Es braucht keine KeenAuth-Umgebung, keinen API-Schlüssel und kein Internet. Alle Daten liegen im Speicher und werden beim Beenden des Prozesses gelöscht.

Der Mock-Server ist nicht KeenAuth. Er implementiert nur die unten genannten Abläufe, genehmigt jede Anfrage automatisch und prüft deutlich weniger als der echte Dienst. Ihre gehostete development-Umgebung ist die einzige Referenz für das reale Verhalten: Führen Sie Ihre End-to-End-Tests vor einer Freigabe gegen sie aus.

Server starten

keenauth mock serve

Standardmäßig lauscht der Server auf 127.0.0.1:4010, einer Loopback-Adresse, die andere Rechner nicht erreichen können. --port ändert den Port (0 wählt einen freien aus). --host ändert die Adresse; jede andere Adresse als eine Loopback-Adresse macht den Mock-Server in Ihrem Netzwerk erreichbar, und die CLI gibt dann eine Warnung aus. Beenden Sie den Server mit Ctrl+C.

Der Befehl liest keine Zugangsdaten und sendet nichts an KeenAuth.

Endpunkte

Pfad Zweck
/.well-known/openid-configuration OIDC-Discovery-Dokument (auch unter /.well-known/oauth-authorization-server bereitgestellt)
/.well-known/jwks.json Öffentlicher Test-Schlüsselsatz (Schlüssel-ID keenauth-test-1)
/oauth2/authorize Autorisierungsendpunkt: Authorization-Code-Ablauf mit PKCE (S256)
/oauth2/token Token-Endpunkt: Grants authorization_code und refresh_token
/oauth2/userinfo UserInfo-Endpunkt (Bearer-Access-Token mit dem Scope openid)
/saml-idp/<application_id>/metadata SAML-2.0-Metadaten des Identity Providers
/saml-idp/<application_id>/sso SAML-Single-Sign-on-Endpunkt (HTTP-Redirect- und HTTP-POST-Bindings)
/scim/v2/<directory_id>/… SCIM-2.0-Endpunkte: Users, Groups, ServiceProviderConfig, ResourceTypes, Schemas
/v1/… In-Memory-Management-API: jede Operation des OpenAPI-Dokuments der Management-API

Die Endpunkt-URLs im Discovery-Dokument und in den SAML-Metadaten verweisen auf die lokale Adresse, auf der der Server lauscht. <application_id> und <directory_id> sind beliebige Namen, die Sie wählen (1–64 Zeichen aus A-Z, a-z, 0-9, -, _); es muss vorher nichts registriert werden.

So verhält er sich

Tokens und Ihr Verifier

Tokens werden mit dem öffentlichen Test-Schlüssel des Offline-Testkits signiert und tragen den Issuer https://test.keenauth.invalid, nicht die lokale Adresse. Die Top-Level-Domain .invalid ist reserviert; ein Produktions-Verifier kann diese Tokens daher nie akzeptieren. Die SDK-Verifier akzeptieren sie nur im Testmodus, den Sie im Testcode ausdrücklich aktivieren.

// Go: accept tokens from the mock server in a test.
v, err := keenauthtest.NewVerifier(verify.Config{Audience: "https://test.keenauth.invalid/oauth2/userinfo"})
if err != nil {
	t.Fatal(err)
}
claims, err := v.VerifyAccessToken(ctx, accessToken, nil)

Verwenden Sie denselben Testmodus in TypeScript (createTestVerifier) und in Python (keenauth.testing.verifier). Generische OIDC-Client-Bibliotheken, die verlangen, dass der issuer des Discovery-Dokuments mit der URL übereinstimmt, von der sie es abgerufen haben, lehnen das Discovery-Dokument des Mock-Servers ab; konfigurieren Sie sie mit den Endpunkt-URLs direkt oder testen Sie sie gegen Ihre development-Umgebung.

Beispiel: der vollständige Ablauf mit curl

Das Beispiel benötigt curl, openssl und jq. Starten Sie den Server zuerst in einem anderen Terminal.

BASE=http://127.0.0.1:4010
REDIRECT=http://127.0.0.1:8080/callback
VERIFIER=$(openssl rand -hex 32)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')

# 1. Authorize: the server answers with a redirect that carries the code.
LOCATION=$(curl -s -o /dev/null -w '%{redirect_url}' --get "$BASE/oauth2/authorize" \
  --data-urlencode response_type=code --data-urlencode client_id=app_local \
  --data-urlencode redirect_uri="$REDIRECT" --data-urlencode 'scope=openid email offline_access' \
  --data-urlencode state=xyz --data-urlencode code_challenge="$CHALLENGE" \
  --data-urlencode code_challenge_method=S256)
CODE=$(printf '%s' "$LOCATION" | sed -E 's/.*[?&]code=([^&]+).*/\1/')

# 2. Exchange the code for tokens.
TOKENS=$(curl -s "$BASE/oauth2/token" -d grant_type=authorization_code -d client_id=app_local \
  --data-urlencode code="$CODE" --data-urlencode redirect_uri="$REDIRECT" -d code_verifier="$VERIFIER")

# 3. Call UserInfo with the access token.
curl -s "$BASE/oauth2/userinfo" -H "Authorization: Bearer $(printf '%s' "$TOKENS" | jq -r .access_token)"

# 4. Refresh.
curl -s "$BASE/oauth2/token" -d grant_type=refresh_token -d client_id=app_local \
  --data-urlencode refresh_token="$(printf '%s' "$TOKENS" | jq -r .refresh_token)"

Schritt 3 gibt den Testbenutzer aus:

{"email":"test.user@example.test","email_verified":true,"sub":"usr_01m3vnbwg0e8mbe7h7xxp6rmkc"}

SAML-Identity-Provider

Verwenden Sie den Mock-Server als Identity Provider eines SAML-Service-Providers, den Sie entwickeln: Verweisen Sie den Service Provider auf die Metadaten-URL, melden Sie sich an, und der Mock-Server antwortet mit einer signierten Antwort für den Testbenutzer. Die Pfade folgen dem gehosteten SAML-Identity-Provider, den KeenAuth für eine spätere Version plant.

Der Signaturschlüssel ist öffentlich. Er wird aus einer veröffentlichten Bezeichnung abgeleitet, daher kann jeder Antworten signieren, die dieses Zertifikat verifiziert. Sein SHA-256-Fingerabdruck ist fcc2fc37b9ddb800721283156f9eb26ff1b80f099d996a65650f8cb469a9be81, und sein Subject enthält TEST ONLY. Registrieren Sie ihn nur in Entwicklungs- und Testkonfigurationen Ihres Service Providers, niemals in der Produktion. KeenAuth erzwingt dies für eigene SAML-Verbindungen: In Staging- und Produktionsumgebungen lehnt es diesen Schlüssel und jede Entity-ID unter .invalid ab, beim Import von Metadaten oder beim Hinzufügen eines Zertifikats und erneut bei der Anmeldung; Entwicklungsumgebungen akzeptieren sie.

Beispiel: eine SAML-Anmeldung mit curl

Das Beispiel benötigt curl und openssl. Starten Sie den Server zuerst in einem anderen Terminal.

BASE=http://127.0.0.1:4010
APP=app_local
ACS=http://127.0.0.1:8080/saml/acs

# 1. IdP metadata: entity ID, signing certificate and SSO URL for your service provider.
curl -s "$BASE/saml-idp/$APP/metadata" -o idp-metadata.xml

# 2. Send an AuthnRequest with the HTTP-POST binding (base64, no compression).
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
REQUEST=$(printf '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_req1" Version="2.0" IssueInstant="%s" AssertionConsumerServiceURL="%s"><saml:Issuer>https://sp.example.com</saml:Issuer></samlp:AuthnRequest>' "$NOW" "$ACS" | openssl base64 -A)
curl -s "$BASE/saml-idp/$APP/sso" --data-urlencode SAMLRequest="$REQUEST" --data-urlencode RelayState=xyz -o form.html

# 3. The page posts SAMLResponse to the ACS URL by itself; decode it to look at the signed response.
sed -n 's/.*name="SAMLResponse" value="\([^"]*\)".*/\1/p' form.html | openssl base64 -d -A > response.xml
grep -o 'InResponseTo="[^"]*"' response.xml | head -1

Der letzte Befehl gibt InResponseTo="_req1" aus. Ihr Service Provider prüft response.xml gegen das Zertifikat in idp-metadata.xml.

SCIM-2.0-Endpunkte

Verwenden Sie die SCIM-Endpunkte, um einen SCIM-Client zu testen, etwa einen Provisionierungs-Connector oder ein Skript, das Benutzer und Gruppen anlegt, ohne Netzwerk. Jedes <directory_id> ist ein eigenes, leeres Verzeichnis, das so lange besteht, wie der Prozess läuft.

Beispiel: einen Benutzer mit curl provisionieren

Das Beispiel benötigt curl und jq. Starten Sie den Server zuerst in einem anderen Terminal.

SCIM=http://127.0.0.1:4010/scim/v2/dir_local
AUTH='Authorization: Bearer keenauth-mock-scim-token'
CT='Content-Type: application/scim+json'

# 1. Create a user; the response carries the id, meta.version and an ETag header.
USER_ID=$(curl -s "$SCIM/Users" -H "$AUTH" -H "$CT" -d '{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "ada@example.test",
  "name": {"givenName": "Ada", "familyName": "Lovelace"},
  "emails": [{"value": "ada@example.test", "type": "work", "primary": true}]
}' | jq -r .id)

# 2. Find it with a filter.
curl -s -G "$SCIM/Users" -H "$AUTH" --data-urlencode 'filter=userName eq "ada@example.test"' | jq .totalResults

# 3. Deactivate it with PATCH, guarded by the version you read.
curl -s -X PATCH "$SCIM/Users/$USER_ID" -H "$AUTH" -H "$CT" -H 'If-Match: W/"1"' -d '{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [{"op": "replace", "path": "active", "value": false}]
}' | jq -c '{active, version: .meta.version}'

# 4. Create a group with the user as a member, then delete the user.
curl -s "$SCIM/Groups" -H "$AUTH" -H "$CT" -d '{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
  "displayName": "Engineering",
  "members": [{"value": "'"$USER_ID"'"}]
}' | jq -c '.members[0] | {display, type}'
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE "$SCIM/Users/$USER_ID" -H "$AUTH"

Die Befehle geben 1, {"active":false,"version":"W/\"2\""}, {"display":"ada@example.test","type":"User"} und 204 aus.

Management-API (/v1)

Verwenden Sie die Management-API-Endpunkte, um Code zu testen, der die KeenAuth-Management-API aufruft (ein SDK, die CLI, ein Deployment-Skript), in CI ohne Netzwerk. Die Pfade sind die gehosteten: Verwenden Sie die Basis-URL http://127.0.0.1:4010/v1 statt der gehosteten, sonst ändert sich nichts. Die Endpunkte werden aus dem OpenAPI-Dokument der Management-API erzeugt, das in der CLI eingebaut ist, daher beantwortet jede Operation dieser API-Version eine Anfrage.

Was sich von der development-Umgebung unterscheidet. Der Mock-Server prüft nur, dass der Body ein JSON-Objekt mit den Pflichtfeldern ist; Feldformate, Längen, Aufzählungen und Eindeutigkeit werden nicht geprüft, und ein Feld mit falschem Typ wird ignoriert. Er ignoriert If-Match, Idempotency-Key und die übrigen Header und liefert kein ETag. Er prüft keine Berechtigungen, Tarifgrenzen, Kontingente, Ratenlimits oder die Region, maskiert keine personenbezogenen Daten, versendet keine E-Mails, führt keine Jobs aus (ein angenommener Job bleibt so, wie er angelegt wurde) und kennt nur eine Umgebung. Datensätze existieren nur für die Lebensdauer des Prozesses, höchstens 10.000 davon. Verwenden Sie Ihre development-Umgebung, um herauszufinden, wie sich die echte API verhält.

Beispiel: anlegen, lesen, ändern und löschen mit curl

Das Beispiel benötigt curl und jq. Starten Sie den Server zuerst in einem anderen Terminal.

API=http://127.0.0.1:4010/v1
AUTH='Authorization: Bearer keenauth-mock-management-token'
CT='Content-Type: application/json'

# 1. Create an organization.
ORG_ID=$(curl -s "$API/organizations" -H "$AUTH" -H "$CT" -d '{"name": "Acme"}' | jq -r .id)

# 2. Read it back and list.
curl -s "$API/organizations/$ORG_ID" -H "$AUTH" | jq -c '{id: (.id | .[0:4]), name}'
curl -s "$API/organizations" -H "$AUTH" | jq -c '{count: (.data | length), list_metadata}'

# 3. Rename it with a merge patch.
curl -s -X PATCH "$API/organizations/$ORG_ID" -H "$AUTH" -H 'Content-Type: application/merge-patch+json' -d '{"name": "Acme Inc"}' | jq -r .name

# 4. Delete it; a second read is a 404 problem.
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE "$API/organizations/$ORG_ID" -H "$AUTH"
curl -s "$API/organizations/$ORG_ID" -H "$AUTH" | jq -c '{status, code}'

# 5. Without the token.
curl -s "$API/organizations" | jq -c '{status, code}'

Die Befehle geben {"id":"org_","name":"Acme"}, {"count":1,"list_metadata":{"after":null,"before":null}}, Acme Inc, 204, {"status":404,"code":"not_found"} und {"status":401,"code":"unauthenticated"} aus.

Nicht unterstützt

Es gibt keine Test-Uhr, kein DPoP, PAR, keine Request-Objects, keinen Device-Flow, keine Client Credentials, kein Token Exchange, kein Logout oder Widerruf und keine gehosteten Seiten. Der SAML-Identity-Provider hat kein Single Logout, keine verschlüsselten Assertions, kein Artifact-Binding und keine IdP-initiierte Anmeldung. Die SCIM-Endpunkte haben keine Bulk-Operationen, keine Sortierung, kein /Me und keine verschachtelten Gruppen, und der Filter kennt kein or, not, keine Klammern und kein gt/ge/lt/le. Testen Sie diese Funktionen in Ihrer development-Umgebung.