Server mock locale
keenauth mock serve avvia sulla tua macchina un piccolo server OpenID Connect, SAML 2.0 e SCIM 2.0, e una versione in memoria dell'API di gestione /v1, per i job CI senza accesso di rete e per lo sviluppo locale. Non richiede un ambiente KeenAuth, una chiave API né Internet. Tutti i dati risiedono in memoria e vengono eliminati quando il processo termina.
Il server mock non è KeenAuth. Implementa solo i flussi elencati qui sotto, approva automaticamente ogni richiesta e verifica molto meno del servizio reale. Il tuo ambiente development ospitato è l'unico riferimento per il comportamento reale: esegui i test end-to-end contro di esso prima di rilasciare.
Avviare il server
keenauth mock serve
Per impostazione predefinita il server ascolta su 127.0.0.1:4010, un indirizzo loopback che altre macchine non possono raggiungere. --port cambia la porta (0 ne sceglie una libera). --host cambia l'indirizzo; qualsiasi indirizzo diverso da un loopback espone il server mock alla tua rete, e la CLI stampa un avviso. Arresta il server con Ctrl+C.
Il comando non legge credenziali e non invia nulla a KeenAuth.
Endpoint
| Percorso | Scopo |
|---|---|
/.well-known/openid-configuration |
Documento di discovery OIDC (servito anche su /.well-known/oauth-authorization-server) |
/.well-known/jwks.json |
Set di chiavi pubbliche di test (ID chiave keenauth-test-1) |
/oauth2/authorize |
Endpoint di autorizzazione: flusso con authorization code e PKCE (S256) |
/oauth2/token |
Endpoint dei token: grant authorization_code e refresh_token |
/oauth2/userinfo |
Endpoint UserInfo (access token Bearer con scope openid) |
/saml-idp/<application_id>/metadata |
Metadati dell'identity provider SAML 2.0 |
/saml-idp/<application_id>/sso |
Endpoint SAML single sign-on (binding HTTP-Redirect e HTTP-POST) |
/scim/v2/<directory_id>/… |
Endpoint SCIM 2.0: Users, Groups, ServiceProviderConfig, ResourceTypes, Schemas |
/v1/… |
API di gestione in memoria: ogni operazione del documento OpenAPI dell'API di gestione |
Gli URL degli endpoint nel documento di discovery e nei metadati SAML puntano all'indirizzo locale su cui ascolta il server. <application_id> e <directory_id> sono nomi a tua scelta (da 1 a 64 caratteri di A-Z, a-z, 0-9, -, _); non occorre registrare nulla in anticipo.
Come si comporta
- Nessuna schermata di consenso, nessuna pagina di accesso. Ogni richiesta di autorizzazione valida viene approvata subito per un utente di test fisso e fittizio e reindirizzata con
code,stateeiss. Ilsubdell'utente èusr_01m3vnbwg0e8mbe7h7xxp6rmkc, l'indirizzo emailtest.user@example.test(verificato) e il nomeTest User. - Nessun client registrato. Viene accettato qualsiasi
client_id. I client secret non vengono verificati.redirect_urideve essere un URIhttpsassoluto, oppure un URIhttpsu un host loopback (127.0.0.1,[::1],localhost), senza frammento; unaredirect_urinon valida o unclient_idmancante producono una pagina di errore invece di un reindirizzamento. - PKCE è obbligatorio. Va inviato
code_challengeconcode_challenge_method=S256;plainviene rifiutato. La richiesta di token deve inviare ilcode_verifiercorrispondente. - I code sono monouso e scadono dopo 60 secondi. Presentare un code una seconda volta fallisce con
invalid_grante revoca ogni token emesso a partire da esso. - Token. L'access token è un JWT RFC 9068 (
typat+jwt), valido per un'ora, consub,client_id,scopee unjticasuale. Il suo audience è il parametroresourcedella richiesta di autorizzazione, oppurehttps://test.keenauth.invalid/oauth2/userinfose non ne è stato inviato uno. Con lo scopeopenidottieni anche un ID token (connonceeauth_time; claimemailenamecon gli scopeemaileprofile). Conoffline_accessottieni un refresh token. - I refresh token ruotano. Ogni refresh restituisce un nuovo refresh token; presentarne uno vecchio fallisce e revoca l'intera catena. Un refresh può restringere lo scope ma non ampliarlo.
- UserInfo accetta solo access token emessi da questo processo, non scaduti né revocati, e che contengono lo scope
openid.
Token e il tuo verificatore
I token sono firmati con la chiave pubblica di test del kit di test offline e portano l'issuer https://test.keenauth.invalid, non l'indirizzo locale. Il dominio di primo livello .invalid è riservato, quindi un verificatore di produzione non può mai accettare questi token: i verificatori degli SDK li accettano solo in modalità test, che attivi esplicitamente nel codice di 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)
Usa la stessa modalità di test in TypeScript (createTestVerifier) e in Python (keenauth.testing.verifier). Le librerie generiche per client OIDC che pretendono che l'issuer del documento di discovery coincida con l'URL da cui è stato recuperato rifiuteranno il documento del server mock; configurale con gli URL degli endpoint direttamente, oppure testale contro il tuo ambiente development.
Esempio: il flusso completo con curl
L'esempio richiede curl, openssl e jq. Avvia prima il server in un altro terminale.
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)"
Il passo 3 stampa l'utente di test:
{"email":"test.user@example.test","email_verified":true,"sub":"usr_01m3vnbwg0e8mbe7h7xxp6rmkc"}
Identity provider SAML
Usa il server mock come identity provider di un service provider SAML che stai sviluppando: punta il service provider all'URL dei metadati, accedi e il server mock risponderà con una risposta firmata per l'utente di test. I percorsi seguono l'identity provider SAML ospitato che KeenAuth prevede per una release successiva.
- Metadati.
GET /saml-idp/<application_id>/metadatarestituisce unEntityDescriptorcon l'entity IDhttps://test.keenauth.invalid/saml-idp/<application_id>, il certificato di firma, i formati NameIDpersistentedemailAddresse l'endpoint SSO per entrambi i binding. Le AuthnRequest firmate non sono richieste (WantAuthnRequestsSigned="false"); una firma sulla richiesta viene ignorata. - SSO. Invia la AuthnRequest con il binding HTTP-Redirect (
GET, DEFLATE + base64 inSAMLRequest) oppure con il binding HTTP-POST (POST, base64). Non c'è una pagina di accesso: una richiesta valida viene approvata subito per l'utente di test e ricevi una pagina HTML che invia da solaSAMLResponse(e il tuoRelayState, al massimo 80 byte) all'URL ACS del service provider. - La AuthnRequest deve contenere
ID,Version="2.0",IssueInstant, unsaml:Issuer(l'entity ID del tuo service provider) eAssertionConsumerServiceURL, perché il server mock non ha service provider registrati. L'URL ACS deve essere un URLhttpsassoluto, oppure un URLhttpsu un host loopback, senza frammento.ProtocolBinding, se presente, deve essere HTTP-POST;Destination, se presente, deve essere l'URL SSO. Ogni altra richiesta riceve400e non viene inviata a nessuna parte. I DTD vengono rifiutati. - La risposta è firmata due volte con RSA-SHA256 e canonicalizzazione esclusiva: l'asserzione, poi la risposta intera.
InResponseToè l'IDdella tua richiesta,Destinatione ilRecipientdella subject confirmation sono l'URL ACS, l'Audienceè l'Issuerdella tua richiesta, e l'asserzione è valida dal momento dell'emissione per 5 minuti. Il NameID èpersistent(un valore stabile per applicazione) salvo che la richiesta chiedaemailAddress. Gli attributi sonoemail,given_name,family_nameename.
La chiave di firma è pubblica. Deriva da un'etichetta pubblicata, quindi chiunque può firmare risposte che questo certificato verifica. La sua impronta SHA-256 è fcc2fc37b9ddb800721283156f9eb26ff1b80f099d996a65650f8cb469a9be81 e il suo subject contiene TEST ONLY. Registrala solo nelle configurazioni di sviluppo e test del tuo service provider, mai in produzione. KeenAuth lo applica alle proprie connessioni SAML: negli ambienti di staging e produzione rifiuta questa chiave e qualsiasi entity ID sotto .invalid, quando importi metadati o aggiungi un certificato, e di nuovo all'accesso; gli ambienti di sviluppo le accettano.
Esempio: un accesso SAML con curl
L'esempio richiede curl e openssl. Avvia prima il server in un altro terminale.
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
L'ultimo comando stampa InResponseTo="_req1". Il tuo service provider convalida response.xml con il certificato in idp-metadata.xml.
Endpoint SCIM 2.0
Usa gli endpoint SCIM per testare un client SCIM, come un connettore di provisioning o uno script che crea utenti e gruppi, senza rete. Ogni <directory_id> è una directory separata e vuota che vive finché vive il processo.
- Autenticazione. Ogni richiesta richiede
Authorization: Bearer keenauth-mock-scim-token. Qualsiasi altra richiesta riceve401con una sfidaWWW-Authenticate: Bearer. Il valore è fisso e pubblico; non è un token SCIM di KeenAuth. - Utenti e gruppi.
POST,GET,PUT,PATCHeDELETEsu/Userse/Groups(RFC 7644).userNamee ildisplayNamedel gruppo sono obbligatori e univoci nella directory, senza distinzione tra maiuscole e minuscole; un duplicato riceve409conscimTypeuniqueness. I membri di un gruppo devono essere utenti della stessa directory. Eliminare un utente lo rimuove dai suoi gruppi.passwordviene accettata e scartata. - Filtri e pagine.
filtersupportaeq,ne,co,sw,ewepruniti conand(per esempiouserName eq "ada@example.test"), oltre astartIndexecount(al massimo 200).attributesedexcludedAttributesselezionano attributi di primo livello.POST /Users/.searche/Groups/.searchaccettano gli stessi parametri nel corpo. - PATCH supporta
add,removeereplacecon percorsi comeactive,name.givenName,emails[type eq "work"].value,members[value eq "<id>"]e attributi dell'estensione enterprise; le operazioni di una richiesta vengono applicate tutte o nessuna. - Versioni. Ogni risorsa riporta
meta.versione un headerETag(W/"1",W/"2", …).If-MatchsuPUT,PATCHeDELETErestituisce412quando la versione è cambiata;If-None-MatchsuGETrestituisce304. - Discovery.
ServiceProviderConfig,ResourceTypeseSchemasdescrivono esattamente questo comportamento./Bulke/Merestituiscono501, come anche in KeenAuth.
Esempio: effettuare il provisioning di un utente con curl
L'esempio richiede curl e jq. Avvia prima il server in un altro terminale.
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"
I comandi stampano 1, {"active":false,"version":"W/\"2\""}, {"display":"ada@example.test","type":"User"} e 204.
API di gestione (/v1)
Usa gli endpoint dell'API di gestione per testare in CI, senza rete, il codice che chiama l'API di gestione di KeenAuth (un SDK, la CLI, uno script di deployment). I percorsi sono quelli ospitati: usa l'URL base http://127.0.0.1:4010/v1 al posto di quello ospitato e nient'altro cambia. Gli endpoint sono generati dal documento OpenAPI dell'API di gestione incluso nella CLI, quindi risponde ogni operazione di quella versione dell'API.
- Autenticazione. Ogni richiesta richiede
Authorization: Bearer keenauth-mock-management-token. Qualsiasi altra richiesta riceve401con una sfidaWWW-Authenticate: Bearer, anche sui percorsi sconosciuti. Il valore è fisso e pubblico; non è una chiave di gestione di KeenAuth. - Risposte. Ogni operazione risponde con lo stato di successo dichiarato dal documento (
200,201,202,204; le due operazioni di download rispondono302con unLocationche non si risolve). Il corpo è ilexampledella risposta di successo nel documento o, finché non ce n'è uno, il più piccolo esempio valido costruito dallo schema della risposta: solo i campi obbligatori, i campi nullable anull, le enumerazioni con il loro primo valore, le stringhe conformi al loropattern. - Stato semplice. Una collezione in cui puoi creare record (
POST /v1/users,/v1/organizations,/v1/applications,/v1/organizations/<org_id>/roles, …) li conserva: il record creato viene restituito daGETsul proprio percorso e nella lista,PATCHePUTlo modificano,DELETElo rimuove insieme a tutto ciò che sta sotto, e un percorso sotto un record inesistente è404. Gli identificatori hanno la forma con prefisso KeenAuth (usr_…,org_…,app_…) e rispettano il pattern del documento; dove il documento non ne indica uno, il prefisso è derivato dal nome del parametro. Le impostazioni che esistono una sola volta per ambiente (per esempio/v1/identity-settings) mantengono ciò che hai scritto conPATCHoPUT. Le risorse che non si possono creare tramite API (per esempio sessioni e report) rispondono sempre con l'esempio. - Liste. Funzionano
limit(da 1 a 100, predefinito 20),afterobeforecon un identificatore di record come cursore, eorder(descdi default, il più recente per primo); la risposta contienelist_metadata. I filtri comestatusnon vengono applicati. - Errori sono
application/problem+jsoncome nel documento, concodee unrequest_idche compare anche nell'headerIdp-Request-Id. Un percorso sconosciuto è404 not_found, un metodo che il percorso non prevede è405 method_not_allowedcon un headerAllow, un corpo che non è JSON valido (o non è un oggetto) è400 malformed_request, unContent-Typediverso da JSON è415sulle operazioni che lo elencano, un campo obbligatorio mancante è422 validation_failedconerrors[], un corpo più grande di 1 MiB è413, e unlimito un cursore non validi sono400.
Cosa cambia rispetto all'ambiente development. Il server mock verifica solo che il corpo sia un oggetto JSON con i campi obbligatori; non controlla formati dei campi, lunghezze, enumerazioni né unicità, e un campo di tipo errato viene ignorato. Ignora If-Match, Idempotency-Key e gli altri header e non restituisce ETag. Non controlla permessi, limiti di piano, quote, rate limit né la regione, non maschera i dati personali, non invia email, non esegue job (un job accettato resta com'era stato creato) e ha un solo ambiente. I record esistono solo per la durata del processo, al massimo 10.000. Usa il tuo ambiente development per scoprire come si comporta l'API reale.
Esempio: creare, leggere, modificare ed eliminare con curl
L'esempio richiede curl e jq. Avvia prima il server in un altro terminale.
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}'
I comandi stampano {"id":"org_","name":"Acme"}, {"count":1,"list_metadata":{"after":null,"before":null}}, Acme Inc, 204, {"status":404,"code":"not_found"} e {"status":401,"code":"unauthenticated"}.
Non supportato
Non ci sono test clock, né DPoP, né PAR, né request object, né device flow, né client credentials, né token exchange, né logout o revoca, né pagine ospitate. L'identity provider SAML non ha single logout, né asserzioni cifrate, né artifact binding, né accesso avviato dall'IdP. Gli endpoint SCIM non hanno operazioni bulk, ordinamento, /Me né gruppi annidati, e il filtro non supporta or, not, parentesi né gt/ge/lt/le. Usa il tuo ambiente development per testarli.