Hi guys,
I'm working on setting up a layered authentication scheme and I've run into a roadblock. I'm hoping someone can point out what I'm missing. I followed this documentation
My Goal:
I want to configure something like this:
-
A device/user enrolls and connects to the network using a certificate
-
Then the user must perform a secondary authentication against our IdP, which is Keycloak.
My understanding is that this should be possible? If I'm mistaken please correct me.
My Setup
I'm using a normal ziti setup with one controller, one router and ZDEW as the tunneler. The integration with Keycloak itself seems to be correct. My External JWT Signer is configured as follows and seems to work (more on that below)
External JWT Signer (keycloak2) (Changed Url's)
{
"name": "keycloak2",
"audience": "openziti",
"issuer": "https://keycloakserver.com/realms/openziti-beta",
"clientId": "openziti-client",
"claimsProperty": "email",
"enabled": true,
"useExternalId": true,
"kid": "",
"externalAuthUrl": "https://keycloakserver.com/realms/openziti-beta",
"scopes": [
"email"
],
"tags": {},
"jwksEndpoint": "https://keycloakserver.com/realms/openziti-beta/protocol/openid-connect/certs",
"targetToken": "ACCESS",
"id": "3FpwHdgPw0kuP4fKDeAX7c"
}
The Authentication Policy
I seem to be stuck between two different problems depending on how I configure the primary section of my Authentication Policy.
Policy #1: extJwt: false
With this policy, the tunneler enrolls and connects with its certificate successfully. The client is stable. However, when I click "authorize IdP" in the tunneler, the OIDC flow completes, but the Ziti controller rejects the authentication with the following error.
Sep 26 10:02:12 ip-172-31-28-70 ziti[645448]: {"authMethod":"ext-jwt","file":"github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:422","func":"github.com/openziti/ziti/controller/model.(*AuthModuleExtJwt).process","level":"error","msg":"encountered 1 candidate JWTs and all failed to validate for primary authentication, see the following log messages","time":"2025-09-26T10:02:12.224Z"}
Sep 26 10:02:12 ip-172-31-28-70 ziti[645448]: {"authMethod":"ext-jwt","authPolicyId":"4QWq74tBKgpVwUHb4HmGj9","error":"primary external jwt processing failed on authentication policy [4QWq74tBKgpVwUHb4HmGj9]: primary external jwt authentication on auth policy is disabled","expectedAudience":"openziti","extJwtSignerId":"3FpwHdgPw0kuP4fKDeAX7c","file":"github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:88","func":"github.com/openziti/ziti/controller/model.(*candidateResult).LogResult","identityId":"FUOTKM5YDc","issuer":"https://keycloakserver.com/realms/openziti-beta","level":"error","msg":"failed to validate candidate JWT at index 0","time":"2025-09-26T10:02:12.224Z","tokenAudiences":"openziti"}
Policy Config:
{
"name": "keycloak2",
"primary": {
"cert": {
"allowExpiredCerts": false,
"allowed": true
},
"extJwt": {
"allowed": false,
"allowedSigners": []
},
"updb": {
"allowed": false,
"lockoutDurationMinutes": 0,
"maxAttempts": 5,
"minPasswordLength": 5,
"requireMixedCase": false,
"requireNumberChar": false,
"requireSpecialChar": false
}
},
"secondary": {
"requireExtJwtSigner": "3FpwHdgPw0kuP4fKDeAX7c",
"requireTotp": false
},
"tags": {}
}
Policy #2: extJwt: true (Causes Tunneler to Freeze)
Based on the error above, I tried enabling extJwt as a primary method. While this seems like it should work, it immediately causes the Ziti Desktop Tunneler to freeze when I click "authorize IdP" in the tunneler. The controller logs are spammed with the error below until I close ZDEW.
Resulting Controller Error (repeating constantly):
Sep 26 10:00:41 ip-172-31-28-70 ziti[645448]: {"authMethod":"ext-jwt","file":"github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:382","func":"github.com/openziti/ziti/controller/model.(*AuthModuleExtJwt).process","level":"error","msg":"encountered 0 candidate JWTs, verification cannot occur","time":"2025-09-26T10:00:41.902Z"}
Sep 26 10:00:41 ip-172-31-28-70 ziti[645448]: {"authMethod":"ext-jwt","file":"github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:382","func":"github.com/openziti/ziti/controller/model.(*AuthModuleExtJwt).process","level":"error","msg":"encountered 0 candidate JWTs, verification cannot occur","time":"2025-09-26T10:00:41.923Z"}
What I've Verified
To ensure my Keycloak and JWT Signer settings are correct, I ran the following command: ziti ops verify ext-jwt-signer oidc keycloak2 --authenticate --controller-url ....
This command completes successfully when I have Ext JWT enabled as a Primary Authentication method.
and ziti ops verify ext-jwt-signer oidc keycloak2 --controller-url seems to give the correct tokens.
My Question
What is the correct authPolicy configuration to achieve something like this:
- Enroll an identity using a certificate.
- Connect with the Ziti Tunneler (authenticating with the cert).
- Click the "authorize IdP" button, log in to Keycloak, and become fully authorized.
- Access services.
And should it work with the keycloak setup shown in this documentation?
Environment Details:
- Ziti Controller Version: v1.6.8
- ZDEW version: 2.7.2.1
- IdP: Keycloak
