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
- Kein Zustimmungsbildschirm, keine Anmeldeseite. Jede gültige Autorisierungsanfrage wird sofort für einen festen, fiktiven Testbenutzer genehmigt und mit
code,stateundisszurückgeleitet. Diesubdes Benutzers istusr_01m3vnbwg0e8mbe7h7xxp6rmkc, die E-Mail-Adressetest.user@example.test(verifiziert) und der NameTest User. - Keine registrierten Clients. Jede
client_idwird akzeptiert. Client-Secrets werden nicht geprüft.redirect_urimuss eine absolutehttps-URI sein oder einehttp-URI auf einem Loopback-Host (127.0.0.1,[::1],localhost), ohne Fragment; eine ungültigeredirect_urioder eine fehlendeclient_idführt zu einer Fehlerseite statt zu einer Weiterleitung. - PKCE ist Pflicht.
code_challengemitcode_challenge_method=S256muss gesendet werden;plainwird abgelehnt. Die Token-Anfrage muss den passendencode_verifiersenden. - Codes sind einmalig verwendbar und laufen nach 60 Sekunden ab. Ein Code, der ein zweites Mal vorgelegt wird, schlägt mit
invalid_grantfehl und widerruft jedes Token, das daraus ausgestellt wurde. - Tokens. Das Access-Token ist ein JWT nach RFC 9068 (
typat+jwt), eine Stunde gültig, mitsub,client_id,scopeund einer zufälligenjti. Seine Audience ist derresource-Parameter der Autorisierungsanfrage oderhttps://test.keenauth.invalid/oauth2/userinfo, wenn keiner gesendet wurde. Mit dem Scopeopeniderhalten Sie zusätzlich ein ID-Token (mitnonceundauth_time; Claimsemailundnamemit den Scopesemailundprofile). Mitoffline_accesserhalten Sie ein Refresh-Token. - Refresh-Tokens rotieren. Jede Erneuerung liefert ein neues Refresh-Token; ein altes vorzulegen schlägt fehl und widerruft die gesamte Kette. Eine Erneuerung darf den Scope einschränken, aber nicht erweitern.
- UserInfo akzeptiert nur Access-Tokens, die dieser Prozess ausgestellt hat, die nicht abgelaufen oder widerrufen sind und den Scope
openidtragen.
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.
- Metadaten.
GET /saml-idp/<application_id>/metadataliefert einEntityDescriptormit der Entity-IDhttps://test.keenauth.invalid/saml-idp/<application_id>, dem Signaturzertifikat, den NameID-FormatenpersistentundemailAddresssowie dem SSO-Endpunkt für beide Bindings. Signierte AuthnRequests sind nicht erforderlich (WantAuthnRequestsSigned="false"); eine Signatur der Anfrage wird ignoriert. - SSO. Senden Sie die AuthnRequest mit dem HTTP-Redirect-Binding (
GET, DEFLATE + Base64 inSAMLRequest) oder dem HTTP-POST-Binding (POST, Base64). Es gibt keine Anmeldeseite: Eine gültige Anfrage wird sofort für den Testbenutzer genehmigt, und Sie erhalten eine HTML-Seite, dieSAMLResponse(und IhrenRelayState, höchstens 80 Byte) selbstständig an die ACS-URL des Service Providers sendet. - Die AuthnRequest muss
ID,Version="2.0",IssueInstant, einsaml:Issuer(die Entity-ID Ihres Service Providers) undAssertionConsumerServiceURLenthalten, weil der Mock-Server keine Service Provider registriert hat. Die ACS-URL muss eine absolutehttps-URL oder einehttp-URL auf einem Loopback-Host sein, ohne Fragment.ProtocolBindingmuss, falls vorhanden, HTTP-POST sein;Destinationmuss, falls vorhanden, die SSO-URL sein. Jede andere Anfrage erhält400und wird nirgendwohin gesendet. DTDs werden abgelehnt. - Die Antwort wird zweimal mit RSA-SHA256 und exklusiver Kanonisierung signiert: die Assertion, dann die gesamte Antwort.
InResponseToist dieIDIhrer Anfrage,Destinationund dasRecipientder Subject-Bestätigung sind die ACS-URL, dieAudienceist derIssuerIhrer Anfrage, und die Assertion ist ab der Ausstellung 5 Minuten gültig. Die NameID istpersistent(ein stabiler Wert pro Anwendung), sofern die Anfrage nichtemailAddressverlangt. Die Attribute sindemail,given_name,family_nameundname.
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.
- Authentifizierung. Jede Anfrage braucht
Authorization: Bearer keenauth-mock-scim-token. Alles andere erhält401mit einerWWW-Authenticate: Bearer-Aufforderung. Der Wert ist fest und öffentlich; er ist kein KeenAuth-SCIM-Token. - Benutzer und Gruppen.
POST,GET,PUT,PATCHundDELETEauf/Usersund/Groups(RFC 7644).userNameund der Gruppen-displayNamesind Pflichtfelder und im Verzeichnis eindeutig, ohne Berücksichtigung der Groß- und Kleinschreibung; ein Duplikat erhält409mitscimTypeuniqueness. Gruppenmitglieder müssen Benutzer desselben Verzeichnisses sein. Das Löschen eines Benutzers entfernt ihn aus seinen Gruppen.passwordwird akzeptiert und verworfen. - Filter und Seiten.
filterunterstützteq,ne,co,sw,ewundpr, verknüpft mitand(zum BeispieluserName eq "ada@example.test"), sowiestartIndexundcount(höchstens 200).attributesundexcludedAttributeswählen Attribute der obersten Ebene aus.POST /Users/.searchund/Groups/.searchnehmen dieselben Parameter im Body entgegen. - PATCH unterstützt
add,removeundreplacemit Pfaden wieactive,name.givenName,emails[type eq "work"].value,members[value eq "<id>"]und Attributen der Enterprise-Erweiterung; die Operationen einer Anfrage werden entweder alle oder keine angewendet. - Versionen. Jede Ressource trägt
meta.versionund einenETag-Header (W/"1",W/"2", …).If-MatchbeiPUT,PATCHundDELETEliefert412, wenn sich die Version geändert hat;If-None-MatchbeiGETliefert304. - Discovery.
ServiceProviderConfig,ResourceTypesundSchemasbeschreiben genau dieses Verhalten./Bulkund/Meliefern501, wie auch in KeenAuth.
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.
- Authentifizierung. Jede Anfrage braucht
Authorization: Bearer keenauth-mock-management-token. Alles andere erhält401mit einerWWW-Authenticate: Bearer-Aufforderung, auch auf unbekannten Pfaden. Der Wert ist fest und öffentlich; er ist kein KeenAuth-Management-Schlüssel. - Antworten. Jede Operation antwortet mit dem Erfolgsstatus, den das Dokument angibt (
200,201,202,204; die beiden Download-Endpunkte antworten mit302und einemLocation, das nicht auflöst). Der Body ist dasexampleder Erfolgsantwort im Dokument oder, solange es keines gibt, das kleinste gültige Beispiel aus dem Antwortschema: nur Pflichtfelder, nullbare Feldernull, Aufzählungen mit ihrem ersten Wert, Zeichenketten, die ihrempatternentsprechen. - Einfacher Zustand. Eine Sammlung, in der Sie Datensätze anlegen können (
POST /v1/users,/v1/organizations,/v1/applications,/v1/organizations/<org_id>/roles, …), behält sie: Der angelegte Datensatz wird vonGETauf seinem eigenen Pfad und in der Liste zurückgegeben,PATCHundPUTändern ihn,DELETEentfernt ihn zusammen mit allem darunter, und ein Pfad unter einem nicht existierenden Datensatz ist404. Bezeichner haben die KeenAuth-Präfixform (usr_…,org_…,app_…) und entsprechen dem Muster im Dokument; wo das Dokument keines angibt, wird das Präfix aus dem Parameternamen abgeleitet. Einstellungen, die es pro Umgebung nur einmal gibt (zum Beispiel/v1/identity-settings), behalten, was Sie mitPATCHoderPUTgeschrieben haben. Ressourcen, die sich über die API nicht anlegen lassen (zum Beispiel Sitzungen und Berichte), antworten immer mit dem Beispiel. - Listen.
limit(1–100, Standard 20),afteroderbeforemit einem Datensatzbezeichner als Cursor sowieorder(Standarddesc, neueste zuerst) funktionieren; die Antwort enthältlist_metadata. Filter wiestatuswerden nicht angewendet. - Fehler sind
application/problem+json, wie im Dokument, mitcodeund einerrequest_id, die auch im HeaderIdp-Request-Idsteht. Ein unbekannter Pfad ist404 not_found, eine Methode, die der Pfad nicht hat, ist405 method_not_allowedmit einemAllow-Header, ein Body, der kein gültiges JSON (oder kein Objekt) ist, ist400 malformed_request, einContent-Typeungleich JSON ist415bei den Operationen, die ihn auflisten, ein fehlendes Pflichtfeld ist422 validation_failedmiterrors[], ein Body größer als 1 MiB ist413, und ein ungültigeslimitoder ein ungültiger Cursor ist400.
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.