-
Notifications
You must be signed in to change notification settings - Fork 25
Description
Summary
When using secondary-req-ext-jwt-signer in an auth policy with an external Keycloak IdP, the Desktop Edge SDK successfully:
- Authenticates via cert (primary) ✅
- Detects auth query for secondary ext-jwt ✅
- Shows "Authorize IdP" button ✅
- Opens browser → user logs into Keycloak ✅
- Receives Keycloak access token ✅
- Shows "Successfully authenticated with external provider" in browser ✅
But then the SDK never sends the token to the controller. Instead, it re-initializes the ext-oidc client and loops back to step 2. The controller receives zero requests from the client after the token is obtained.
Environment
| Component | Version |
|---|---|
| Ziti Controller | v1.6.12 |
| Ziti Edge Router | v1.6.12 |
| Desktop Edge (Windows) | 2.9.3.0 |
| ziti-edge-tunnel service | v1.9.9+ (bundled) |
| ziti-sdk-c | v0.40.9 (bundled) |
| External IdP | Keycloak ~25.0.6 |
| OS | Windows 11 |
Configuration
ext-jwt-signer
{
"name": "keycloak-xxx",
"issuer": "https://auth.example.com/realms/MyRealm",
"audience": "OpenZiti",
"claimsProperty": "email",
"useExternalId": true,
"clientId": "OpenZiti",
"scopes": ["openid", "email"],
"externalAuthUrl": "https://auth.example.com/realms/MyRealm",
"enabled": true
}Auth Policy (secondary required)
{
"name": "keycloak-required",
"primary": {
"cert": { "allowed": true },
"extJwt": { "allowed": true, "allowedSigners": ["<signer-id>"] }
},
"secondary": {
"requireExtJwtSigner": "<signer-id>"
}
}Identity
- Type: Default
- Has enrolled cert ✅
- Has
externalIdset to user's email ✅ - Auth policy:
keycloak-required
Keycloak Client
- Public client (no client secret)
- Standard flow + Direct access grants enabled
- Token contains correct
iss,aud("OpenZiti"),emailclaims ✅ - Valid redirect URIs:
http://localhost/*
Controller Config
edge-oidcAPI binding enabled- OIDC discovery endpoint responding ✅
What Happens (SDK service log)
Step 1: Cert auth → partially authenticated ✅
INFO ziti.c:2076 version_pre_auth_cb() ztx[1] using OIDC authentication method
INFO oidc.c:88 oidc_client_init() oidc[internal] initializing with provider[https://controller:1280/oidc]
WARN ziti_tunnel_ctrl.c:1018 ziti_ctx controller connections failed: api session is partially authenticated, waiting for auth query resolution
Step 2: ext auth event → "Authorize IdP" shown ✅
INFO ext_oidc.c:141 ext_oidc_client_init() oidc[keycloak-xxx] initializing with provider[https://auth.example.com/realms/MyRealm]
INFO ziti_tunnel_ctrl.c:1139 ext auth event received
INFO ziti-edge-tunnel.c:804 ext auth: login_with_ext_signer
Step 3: User clicks authorize → Keycloak login → token received ✅
INFO external_auth.c:85 received link request: https://auth.example.com/realms/MyRealm/protocol/openid-connect/auth?client_id=OpenZiti&scope=openid%20email%20openid&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A20314%2Fauth%2Fcallback&...
INFO ext_oidc.c:324 request_token() requesting token path[https://auth.example.com/realms/MyRealm/protocol/openid-connect/token] auth[...]
Browser shows: "Successfully authenticated with external provider. You may close this page."
Step 4: SDK LOOPS instead of presenting token to controller ❌
INFO ext_oidc.c:141 ext_oidc_client_init() oidc[keycloak-xxx] initializing with provider[https://auth.example.com/realms/MyRealm]
INFO ziti_tunnel_ctrl.c:1139 ext auth event received
INFO ziti-edge-tunnel.c:804 ext auth: login_with_ext_signer
Time between token received and loop: ~0.6 seconds.
Controller log during this window: ZERO requests from client.
What I Verified
Direct ext-jwt auth via REST API — WORKS ✅
KC_TOKEN=$(curl -s -X POST "https://auth.example.com/realms/MyRealm/protocol/openid-connect/token" \
-d "grant_type=password" -d "client_id=OpenZiti" \
-d "username=testuser" -d "password=***" \
-d "scope=openid email" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s -X POST "https://controller:1280/edge/client/v1/authenticate?method=ext-jwt" \
-H "Authorization: Bearer $KC_TOKEN" -H "Content-Type: application/json" -d '{}'
# Result: ✅ 200 OK — session created, identity resolved via externalIdThis proves the token is valid and the controller can authenticate with it.
Auth query present in API session ✅
GET /edge/client/v1/current-api-session{
"authQueries": [
{
"typeId": "EXT-JWT",
"provider": "url",
"httpUrl": "https://auth.example.com/realms/MyRealm",
"clientId": "OpenZiti",
"scopes": ["email", "openid"],
"id": "<signer-id>"
}
]
}Primary ext-jwt (cert=false) also fails ❌
When auth policy has primary.cert.allowed=false, SDK still sends cert first → rejected:
ERROR authenticator_mod_cert.go:290 "invalid certificate authentication, not allowed by auth policy"
SDK never falls through to ext-jwt primary auth.
Expected Behavior
After SDK receives external IdP token (step 3), it should:
- Present the token to the controller to resolve the secondary auth query
- Controller validates JWT against ext-jwt-signer config
- Auth query resolved → session fully authenticated
- Services become available
Actual Behavior
After SDK receives external IdP token, it:
- Does NOT send token to controller (controller receives zero requests)
- Re-initializes
ext_oidc_client - Fires new
ext auth event - Shows "Authorize IdP" again → infinite loop
Questions
- Is there a known API endpoint for resolving secondary ext-jwt auth queries? I could not find one in the Swagger spec.
- Is
secondary-req-ext-jwt-signerwith external IdPs supported in v1.6.12, or is this a planned feature? - Should this be filed against
ziti-sdk-cordesktop-edge-win?
Workaround
Using auth policy with primary.cert.allowed=true + primary.extJwt.allowed=true + NO secondary requirement. Identity authenticates via cert only. Keycloak integration works at REST API level but not through the tunnel client.