Servidor mock local
keenauth mock serve ejecuta en su máquina un pequeño servidor de OpenID Connect, SAML 2.0 y SCIM 2.0, y una versión en memoria de la API de gestión /v1, para trabajos de CI sin acceso a la red y para el desarrollo local. No necesita un entorno de KeenAuth, ninguna clave de API ni Internet. Todos los datos viven en memoria y se eliminan cuando el proceso termina.
El servidor mock no es KeenAuth. Implementa solo los flujos que se indican abajo, aprueba automáticamente cada solicitud y comprueba bastante menos que el servicio real. Su entorno development alojado es la única referencia para el comportamiento real: ejecute sus pruebas de extremo a extremo contra él antes de publicar.
Iniciar el servidor
keenauth mock serve
De forma predeterminada, el servidor escucha en 127.0.0.1:4010, una dirección de bucle local a la que otras máquinas no pueden acceder. --port cambia el puerto (0 elige uno libre). --host cambia la dirección; cualquier dirección que no sea de bucle local expone el servidor mock a su red, y la CLI muestra una advertencia. Detenga el servidor con Ctrl+C.
El comando no lee credenciales y no envía nada a KeenAuth.
Endpoints
| Ruta | Función |
|---|---|
/.well-known/openid-configuration |
Documento de descubrimiento OIDC (también servido en /.well-known/oauth-authorization-server) |
/.well-known/jwks.json |
Conjunto de claves públicas de prueba (ID de clave keenauth-test-1) |
/oauth2/authorize |
Endpoint de autorización: flujo de código de autorización con PKCE (S256) |
/oauth2/token |
Endpoint de token: concesiones authorization_code y refresh_token |
/oauth2/userinfo |
Endpoint UserInfo (token de acceso Bearer con el scope openid) |
/saml-idp/<application_id>/metadata |
Metadatos del proveedor de identidad SAML 2.0 |
/saml-idp/<application_id>/sso |
Endpoint de inicio de sesión único SAML (bindings HTTP-Redirect y HTTP-POST) |
/scim/v2/<directory_id>/… |
Endpoints SCIM 2.0: Users, Groups, ServiceProviderConfig, ResourceTypes, Schemas |
/v1/… |
API de gestión en memoria: cada operación del documento OpenAPI de la API de gestión |
Las URL de los endpoints del documento de descubrimiento y de los metadatos SAML apuntan a la dirección local en la que escucha el servidor. <application_id> y <directory_id> son nombres que usted elige (de 1 a 64 caracteres de A-Z, a-z, 0-9, -, _); no hace falta registrar nada antes.
Cómo se comporta
- Sin pantalla de consentimiento ni página de inicio de sesión. Cada solicitud de autorización válida se aprueba de inmediato para un usuario de prueba fijo y ficticio, y se redirige de vuelta con
code,stateeiss. Elsubdel usuario esusr_01m3vnbwg0e8mbe7h7xxp6rmkc, la dirección de correotest.user@example.test(verificada) y el nombreTest User. - Sin clientes registrados. Se acepta cualquier
client_id. Los secretos de cliente no se comprueban.redirect_uridebe ser una URIhttpsabsoluta, o una URIhttpen un host de bucle local (127.0.0.1,[::1],localhost), sin fragmento; unaredirect_urino válida o unclient_idausente devuelve una página de error en lugar de una redirección. - PKCE es obligatorio. Hay que enviar
code_challengeconcode_challenge_method=S256;plainse rechaza. La solicitud de token debe enviar elcode_verifiercorrespondiente. - Los códigos son de un solo uso y caducan a los 60 segundos. Presentar un código por segunda vez falla con
invalid_granty revoca todos los tokens emitidos a partir de él. - Tokens. El token de acceso es un JWT según RFC 9068 (
typat+jwt), válido durante una hora, consub,client_id,scopey unjtialeatorio. Su audiencia es el parámetroresourcede la solicitud de autorización, ohttps://test.keenauth.invalid/oauth2/userinfosi no se envió ninguno. Con el scopeopenidtambién obtiene un token de ID (connonceyauth_time; las claimsemailynamecon los scopesemailyprofile). Conoffline_accessobtiene un token de actualización. - Los tokens de actualización rotan. Cada actualización devuelve un token de actualización nuevo; presentar uno antiguo falla y revoca toda la cadena. Una actualización puede reducir el scope, pero no ampliarlo.
- UserInfo acepta solo tokens de acceso emitidos por este proceso, que no hayan caducado ni sido revocados, y que lleven el scope
openid.
Tokens y su verificador
Los tokens se firman con la clave pública de prueba del kit de pruebas sin conexión y llevan el emisor https://test.keenauth.invalid, no la dirección local. El dominio de primer nivel .invalid está reservado, así que un verificador de producción nunca puede aceptar estos tokens: los verificadores del SDK solo los aceptan en modo de prueba, que usted activa explícitamente en el código de prueba.
// 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)
Use el mismo modo de prueba en TypeScript (createTestVerifier) y en Python (keenauth.testing.verifier). Las bibliotecas genéricas de cliente OIDC que exigen que el issuer del documento de descubrimiento sea igual a la URL desde la que se obtuvo rechazarán el documento del servidor mock; configúrelas con las URL de los endpoints directamente, o pruébelas contra su entorno development.
Ejemplo: el flujo completo con curl
El ejemplo necesita curl, openssl y jq. Inicie primero el servidor en otra 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)"
El paso 3 muestra el usuario de prueba:
{"email":"test.user@example.test","email_verified":true,"sub":"usr_01m3vnbwg0e8mbe7h7xxp6rmkc"}
Proveedor de identidad SAML
Use el servidor mock como proveedor de identidad de un proveedor de servicios SAML que esté desarrollando: apunte el proveedor de servicios a la URL de metadatos, inicie sesión y el servidor mock responderá con una respuesta firmada para el usuario de prueba. Las rutas siguen al proveedor de identidad SAML alojado que KeenAuth planea para una versión posterior.
- Metadatos.
GET /saml-idp/<application_id>/metadatadevuelve unEntityDescriptorcon el ID de entidadhttps://test.keenauth.invalid/saml-idp/<application_id>, el certificado de firma, los formatos de NameIDpersistentyemailAddress, y el endpoint SSO para ambos bindings. No se exigen AuthnRequest firmadas (WantAuthnRequestsSigned="false"); una firma en la solicitud se ignora. - SSO. Envíe la AuthnRequest con el binding HTTP-Redirect (
GET, DEFLATE + base64 enSAMLRequest) o con el binding HTTP-POST (POST, base64). No hay página de inicio de sesión: una solicitud válida se aprueba de inmediato para el usuario de prueba y usted recibe una página HTML que envía por sí solaSAMLResponse(y suRelayState, de como máximo 80 bytes) a la URL ACS del proveedor de servicios. - La AuthnRequest debe incluir
ID,Version="2.0",IssueInstant, unsaml:Issuer(el ID de entidad de su proveedor de servicios) yAssertionConsumerServiceURL, porque el servidor mock no tiene proveedores de servicios registrados. La URL ACS debe ser una URLhttpsabsoluta, o una URLhttpen un host de bucle local, sin fragmento.ProtocolBinding, si está presente, debe ser HTTP-POST;Destination, si está presente, debe ser la URL de SSO. Cualquier otra solicitud recibe400y no se envía a ningún sitio. Los DTD se rechazan. - La respuesta se firma dos veces con RSA-SHA256 y canonicalización exclusiva: la aserción y, después, la respuesta completa.
InResponseToes elIDde su solicitud,Destinationy elRecipientde la confirmación del sujeto son la URL ACS, elAudiencees elIssuerde su solicitud, y la aserción es válida desde el momento de su emisión durante 5 minutos. El NameID espersistent(un valor estable por aplicación) salvo que la solicitud pidaemailAddress. Los atributos sonemail,given_name,family_nameyname.
La clave de firma es pública. Se deriva de una etiqueta publicada, así que cualquiera puede firmar respuestas que este certificado verifica. Su huella SHA-256 es fcc2fc37b9ddb800721283156f9eb26ff1b80f099d996a65650f8cb469a9be81 y su subject dice TEST ONLY. Regístrela solo en configuraciones de desarrollo y prueba de su proveedor de servicios, nunca en producción. KeenAuth lo aplica a sus propias conexiones SAML: en entornos de staging y producción rechaza esta clave y cualquier ID de entidad bajo .invalid, al importar metadatos o añadir un certificado, y de nuevo al iniciar sesión; los entornos de desarrollo las aceptan.
Ejemplo: un inicio de sesión SAML con curl
El ejemplo necesita curl y openssl. Inicie primero el servidor en otra 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
El último comando muestra InResponseTo="_req1". Su proveedor de servicios valida response.xml con el certificado de idp-metadata.xml.
Endpoints SCIM 2.0
Use los endpoints SCIM para probar un cliente SCIM, como un conector de aprovisionamiento o un script que crea usuarios y grupos, sin red. Cada <directory_id> es un directorio separado y vacío que vive mientras dure el proceso.
- Autenticación. Cada solicitud necesita
Authorization: Bearer keenauth-mock-scim-token. Cualquier otra cosa recibe401con un desafíoWWW-Authenticate: Bearer. El valor es fijo y público; no es un token SCIM de KeenAuth. - Usuarios y grupos.
POST,GET,PUT,PATCHyDELETEsobre/Usersy/Groups(RFC 7644).userNamey eldisplayNamedel grupo son obligatorios y únicos en el directorio, sin distinguir mayúsculas; un duplicado recibe409conscimTypeuniqueness. Los miembros de un grupo deben ser usuarios del mismo directorio. Eliminar un usuario lo quita de sus grupos.passwordse acepta y se descarta. - Filtros y páginas.
filteradmiteeq,ne,co,sw,ewyprunidos conand(por ejemplouserName eq "ada@example.test"), además destartIndexycount(como máximo 200).attributesyexcludedAttributesseleccionan atributos de primer nivel.POST /Users/.searchy/Groups/.searchaceptan los mismos parámetros en el cuerpo. - PATCH admite
add,removeyreplacecon rutas comoactive,name.givenName,emails[type eq "work"].value,members[value eq "<id>"]y atributos de la extensión enterprise; las operaciones de una solicitud se aplican todas o ninguna. - Versiones. Cada recurso lleva
meta.versiony una cabeceraETag(W/"1",W/"2", …).If-MatchenPUT,PATCHyDELETEdevuelve412cuando la versión ha cambiado;If-None-MatchenGETdevuelve304. - Descubrimiento.
ServiceProviderConfig,ResourceTypesySchemasdescriben exactamente este comportamiento./Bulky/Medevuelven501, como también en KeenAuth.
Ejemplo: aprovisionar un usuario con curl
El ejemplo necesita curl y jq. Inicie primero el servidor en otra 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"
Los comandos muestran 1, {"active":false,"version":"W/\"2\""}, {"display":"ada@example.test","type":"User"} y 204.
API de gestión (/v1)
Use los endpoints de la API de gestión para probar en CI, sin red, el código que llama a la API de gestión de KeenAuth (un SDK, la CLI, un script de despliegue). Las rutas son las alojadas: use la URL base http://127.0.0.1:4010/v1 en lugar de la alojada y nada más cambia. Los endpoints se generan a partir del documento OpenAPI de la API de gestión incluido en la CLI, así que responde cada operación de esa versión de la API.
- Autenticación. Cada solicitud necesita
Authorization: Bearer keenauth-mock-management-token. Cualquier otra cosa recibe401con un desafíoWWW-Authenticate: Bearer, también en rutas desconocidas. El valor es fijo y público; no es una clave de gestión de KeenAuth. - Respuestas. Cada operación responde con el código de éxito que declara el documento (
200,201,202,204; las dos operaciones de descarga responden302con unLocationque no se resuelve). El cuerpo es elexamplede la respuesta de éxito del documento o, mientras no lo haya, el ejemplo válido más pequeño construido a partir del esquema de la respuesta: solo los campos obligatorios, los campos nulables ennull, las enumeraciones con su primer valor y las cadenas que cumplen supattern. - Estado sencillo. Una colección en la que puede crear registros (
POST /v1/users,/v1/organizations,/v1/applications,/v1/organizations/<org_id>/roles, …) los conserva: el registro creado lo devuelveGETen su propia ruta y en la lista,PATCHyPUTlo modifican,DELETElo elimina junto con todo lo que hay debajo, y una ruta bajo un registro que no existe es404. Los identificadores tienen la forma de prefijo de KeenAuth (usr_…,org_…,app_…) y cumplen el patrón del documento; cuando el documento no da ninguno, el prefijo se deriva del nombre del parámetro. Los ajustes que existen una sola vez por entorno (por ejemplo/v1/identity-settings) conservan lo que escribió conPATCHoPUT. Los recursos que no se pueden crear por la API (por ejemplo sesiones e informes) responden siempre con el ejemplo. - Listas. Funcionan
limit(de 1 a 100, por defecto 20),afterobeforecon un identificador de registro como cursor, yorder(descpor defecto, el más reciente primero); la respuesta incluyelist_metadata. Los filtros comostatusno se aplican. - Los errores son
application/problem+jsoncomo en el documento, concodey unrequest_idque también aparece en la cabeceraIdp-Request-Id. Una ruta desconocida es404 not_found, un método que la ruta no tiene es405 method_not_allowedcon una cabeceraAllow, un cuerpo que no es JSON válido (o no es un objeto) es400 malformed_request, unContent-Typedistinto de JSON es415en las operaciones que lo listan, un campo obligatorio ausente es422 validation_failedconerrors[], un cuerpo de más de 1 MiB es413, y unlimito un cursor no válido es400.
En qué se diferencia del entorno development. El servidor mock solo valida que el cuerpo sea un objeto JSON con los campos obligatorios; no se comprueban los formatos de los campos, las longitudes, las enumeraciones ni la unicidad, y un campo con el tipo equivocado se ignora. Ignora If-Match, Idempotency-Key y el resto de cabeceras y no devuelve ETag. No comprueba permisos, límites de plan, cuotas, límites de frecuencia ni la región, no enmascara datos personales, no envía correos, no ejecuta trabajos (un trabajo aceptado permanece como se creó) y tiene un solo entorno. Los registros existen solo mientras dure el proceso, como máximo 10.000. Use su entorno development para saber cómo se comporta la API real.
Ejemplo: crear, leer, modificar y eliminar con curl
El ejemplo necesita curl y jq. Inicie primero el servidor en otra 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}'
Los comandos muestran {"id":"org_","name":"Acme"}, {"count":1,"list_metadata":{"after":null,"before":null}}, Acme Inc, 204, {"status":404,"code":"not_found"} y {"status":401,"code":"unauthenticated"}.
No compatible
No hay reloj de pruebas, ni DPoP, ni PAR, ni objetos de solicitud, ni flujo de dispositivo, ni client credentials, ni intercambio de tokens, ni cierre de sesión o revocación, ni páginas alojadas. El proveedor de identidad SAML no tiene cierre de sesión único, ni aserciones cifradas, ni binding de artefacto, ni inicio de sesión iniciado por el IdP. Los endpoints SCIM no tienen operaciones masivas, ordenación, /Me ni grupos anidados, y el filtro no admite or, not, paréntesis ni gt/ge/lt/le. Use su entorno development para probarlos.