Serveur mock local

keenauth mock serve lance sur votre machine un petit serveur OpenID Connect, SAML 2.0 et SCIM 2.0, ainsi qu'une version en mémoire de l'API de gestion /v1, pour les tâches CI sans accès réseau et pour le développement local. Il n'a besoin ni d'environnement KeenAuth, ni de clé API, ni d'Internet. Toutes les données sont conservées en mémoire et supprimées à la fin du processus.

Le serveur mock n'est pas KeenAuth. Il n'implémente que les flux ci-dessous, approuve automatiquement chaque requête et vérifie beaucoup moins que le service réel. Votre environnement development hébergé est la seule référence pour le comportement réel : exécutez vos tests de bout en bout contre lui avant toute mise en production.

Démarrer le serveur

keenauth mock serve

Par défaut, le serveur écoute sur 127.0.0.1:4010, une adresse de boucle locale qu'aucune autre machine ne peut atteindre. --port change le port (0 en choisit un libre). --host change l'adresse ; toute adresse autre qu'une adresse de boucle locale expose le serveur mock à votre réseau, et la CLI affiche un avertissement. Arrêtez le serveur avec Ctrl+C.

La commande ne lit aucun identifiant et n'envoie rien à KeenAuth.

Points de terminaison

Chemin Rôle
/.well-known/openid-configuration Document de découverte OIDC (également servi sur /.well-known/oauth-authorization-server)
/.well-known/jwks.json Jeu de clés publiques de test (identifiant de clé keenauth-test-1)
/oauth2/authorize Point de terminaison d'autorisation : flux par code d'autorisation avec PKCE (S256)
/oauth2/token Point de terminaison de jetons : grants authorization_code et refresh_token
/oauth2/userinfo Point de terminaison UserInfo (jeton d'accès Bearer avec le scope openid)
/saml-idp/<application_id>/metadata Métadonnées du fournisseur d'identité SAML 2.0
/saml-idp/<application_id>/sso Point de terminaison SSO SAML (liaisons HTTP-Redirect et HTTP-POST)
/scim/v2/<directory_id>/… Points de terminaison SCIM 2.0 : Users, Groups, ServiceProviderConfig, ResourceTypes, Schemas
/v1/… API de gestion en mémoire : chaque opération du document OpenAPI de l'API de gestion

Les URL des points de terminaison du document de découverte et des métadonnées SAML pointent vers l'adresse locale sur laquelle écoute le serveur. <application_id> et <directory_id> sont des noms de votre choix (1 à 64 caractères parmi A-Z, a-z, 0-9, -, _) ; rien n'a besoin d'être enregistré au préalable.

Comportement

Jetons et votre vérificateur

Les jetons sont signés avec la clé publique de test du kit de test hors ligne et portent l'émetteur https://test.keenauth.invalid, et non l'adresse locale. Le domaine de premier niveau .invalid est réservé : un vérificateur de production ne peut donc jamais accepter ces jetons. Les vérificateurs des SDK ne les acceptent qu'en mode test, que vous activez explicitement dans le code de test.

// 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)

Utilisez le même mode test en TypeScript (createTestVerifier) et en Python (keenauth.testing.verifier). Les bibliothèques génériques de client OIDC qui exigent que l'issuer du document de découverte soit égal à l'URL à partir de laquelle il a été récupéré refuseront le document du serveur mock ; configurez-les directement avec les URL des points de terminaison, ou testez-les contre votre environnement development.

Exemple : le flux complet avec curl

L'exemple nécessite curl, openssl et jq. Démarrez d'abord le serveur dans un autre 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)"

L'étape 3 affiche l'utilisateur de test :

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

Fournisseur d'identité SAML

Utilisez le serveur mock comme fournisseur d'identité d'un fournisseur de services SAML que vous développez : indiquez au fournisseur de services l'URL des métadonnées, connectez-vous, et le serveur mock répond par une réponse signée pour l'utilisateur de test. Les chemins suivent le fournisseur d'identité SAML hébergé que KeenAuth prévoit pour une version ultérieure.

La clé de signature est publique. Elle est dérivée d'un libellé publié, donc n'importe qui peut signer des réponses que ce certificat vérifie. Son empreinte SHA-256 est fcc2fc37b9ddb800721283156f9eb26ff1b80f099d996a65650f8cb469a9be81 et son sujet indique TEST ONLY. Ne l'enregistrez que dans les configurations de développement et de test de votre fournisseur de services, jamais en production. KeenAuth l'applique à ses propres connexions SAML : dans les environnements de staging et de production, il refuse cette clé et tout identifiant d'entité sous .invalid, à l'import des métadonnées ou à l'ajout d'un certificat, puis à nouveau à la connexion ; les environnements de développement les acceptent.

Exemple : une connexion SAML avec curl

L'exemple nécessite curl et openssl. Démarrez d'abord le serveur dans un autre 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

La dernière commande affiche InResponseTo="_req1". Votre fournisseur de services valide response.xml avec le certificat présent dans idp-metadata.xml.

Points de terminaison SCIM 2.0

Utilisez les points de terminaison SCIM pour tester un client SCIM, tel qu'un connecteur de provisionnement ou un script qui crée des utilisateurs et des groupes, sans réseau. Chaque <directory_id> est un annuaire distinct et vide, qui vit aussi longtemps que le processus.

Exemple : provisionner un utilisateur avec curl

L'exemple nécessite curl et jq. Démarrez d'abord le serveur dans un autre 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"

Les commandes affichent 1, {"active":false,"version":"W/\"2\""}, {"display":"ada@example.test","type":"User"} et 204.

API de gestion (/v1)

Utilisez les points de terminaison de l'API de gestion pour tester, en CI et sans réseau, le code qui appelle l'API de gestion de KeenAuth (un SDK, la CLI, un script de déploiement). Les chemins sont ceux de l'environnement hébergé : utilisez l'URL de base http://127.0.0.1:4010/v1 à la place de l'URL hébergée, rien d'autre ne change. Les points de terminaison sont générés à partir du document OpenAPI de l'API de gestion intégré à la CLI, donc chaque opération de cette version de l'API répond.

Ce qui diffère de l'environnement development. Le serveur mock vérifie seulement que le corps est un objet JSON contenant les champs obligatoires ; les formats de champs, les longueurs, les énumérations et l'unicité ne sont pas contrôlés, et un champ au mauvais type est ignoré. Il ignore If-Match, Idempotency-Key et les autres en-têtes et ne renvoie pas d'ETag. Il ne contrôle ni les permissions, ni les limites de forfait, ni les quotas, ni les limites de débit, ni la région ; il ne masque pas les données personnelles, n'envoie aucun e-mail, n'exécute aucune tâche (une tâche acceptée reste dans l'état où elle a été créée) et ne dispose que d'un seul environnement. Les enregistrements n'existent que pendant la durée du processus, au maximum 10 000. Utilisez votre environnement development pour savoir comment se comporte la véritable API.

Exemple : créer, lire, modifier et supprimer avec curl

L'exemple nécessite curl et jq. Démarrez d'abord le serveur dans un autre 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}'

Les commandes affichent {"id":"org_","name":"Acme"}, {"count":1,"list_metadata":{"after":null,"before":null}}, Acme Inc, 204, {"status":404,"code":"not_found"} et {"status":401,"code":"unauthenticated"}.

Non pris en charge

Il n'y a pas d'horloge de test, ni DPoP, ni PAR, ni objets de requête, ni flux device, ni client credentials, ni échange de jetons, ni déconnexion ou révocation, ni pages hébergées. Le fournisseur d'identité SAML n'a ni déconnexion unique, ni assertions chiffrées, ni liaison par artefact, ni connexion initiée par l'IdP. Les points de terminaison SCIM n'ont ni opérations en masse, ni tri, ni /Me, ni groupes imbriqués, et le filtre ne connaît ni or, ni not, ni parenthèses, ni gt/ge/lt/le. Utilisez votre environnement development pour les tester.