"enrollToTokenEnabled" not creating user with JIT

Hello, new here, I just installed the ziti-controller (v1.8.0) on Kubernetes with the Helm chart. I also currently have a Let's Encrypt certificate for the webBindingPki.

I’m trying to setup my IDP (Authentik) to work with Ziti, and to provision users with just-in-time (JIT) through OIDC/JWT, as I saw it’s now possible with the 1.8.0 release.

My IDP seem correctly setup with Ziti, I can select Authentik in my desktop-edge-win client and authenticate with it (the redirection/callback URL all work). The only problem is that if my user isn’t created BEFORE the authentication, then I got the following error:

{"authMethod":"ext-jwt","authenticatorId":"","externalId":"astro@mydomain.com","file":"github.com/openziti/ziti/controller/model/authenticator_mod_cert.go:536","func":"github.com/openziti/ziti/controller/model.getAuthPolicyByExternalId","level":"error","msg":"identity not found by externalId","time":"2025-12-16T18:48:43.618Z"}

{"authMethod":"ext-jwt","file":"github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:229","func":"github.com/openziti/ziti/controller/model.(*AuthModuleExtJwt).process","level":"error","msg":"encountered 1 candidate and all failed to validate for primary authentication, see the following log messages","time":"2025-12-16T18:48:43.618Z"}

{"authMethod":"ext-jwt","error":"error during authentication policy and identity lookup by claims type [external id] and claim id [astro@mydomain.com]: INVALID_AUTH: The authentication request failed","expectedAudience":"openziti","file":"github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:84","func":"github.com/openziti/ziti/controller/model.(*AuthTokenVerificationResult).LogResult","issuer":"https://auth.mydomain.com/application/o/openziti/","level":"error","msg":"failed to validate candidate JWT at index 0","time":"2025-12-16T18:48:43.620Z","tokenAudiences":["openziti"],"tokenIssuerId":"6K1GTY5jkXySS8Kgd2LMsw","tokenIssuerType":"externalJwtSigner"}

Which is strange because I taught that it was now possible with the v1.8.0… Here is my config for my JWT Signers:

{
  "_links": {
    "self": {
      "href": "./external-jwt-signers/6K1GTY5jkXySS8Kgd2LMsw"
    }
  },
  "createdAt": "2025-12-16T18:48:08.046Z",
  "id": "6K1GTY5jkXySS8Kgd2LMsw",
  "tags": {},
  "updatedAt": "2025-12-16T18:48:08.046Z",
  "audience": "openziti",
  "certPem": null,
  "claimsProperty": "email",
  "clientId": "openziti",
  "commonName": "",
  "enabled": true,
  "enrollAttributeClaimsSelector": "email",
  "enrollAuthPolicyId": "6WXHkQkdxh0yGi9MzTqdMX",
  "enrollNameClaimsSelector": "email",
  "enrollToTokenEnabled": true,
  "externalAuthUrl": "https://auth.mydomain.com/application/o/openziti/",
  "fingerprint": null,
  "issuer": "https://auth.mydomain.com/application/o/openziti/",
  "jwksEndpoint": "https://auth.mydomain.com/application/o/openziti/jwks/",
  "kid": null,
  "name": "authentik",
  "notAfter": "0001-01-01T00:00:00.000Z",
  "notBefore": "0001-01-01T00:00:00.000Z",
  "scopes": [
    "email",
    "openid"
  ],
  "targetToken": "ACCESS",
  "useExternalId": true
}

As you can see I’ve enabled “enrollToTokenEnabled“ which should provision and enroll my users, but it doesn’t seem to work…

Thank you all !

Welcome, @astro!

Did you set the Helm Chart's image.tag=1.8.0-pre3 to use the 1.8.0 prerelease? BTW, I do plan to make 1.8.0 the default version when it is a stable release.

Based on your log messages, I'm guessing no identity has externalId == "astro@mydomain.com":

  {
    "authMethod": "ext-jwt",
    "authenticatorId": "",
    "externalId": "astro@mydomain.com",
    "file": "github.com/openziti/ziti/controller/model/authenticator_mod_cert.go:536",
    "func": "github.com/openziti/ziti/controller/model.getAuthPolicyByExternalId",
    "level": "error",
    "msg": "identity not found by externalId",
    "time": "2025-12-16T18:48:43.618Z"
  },
  {
    "authMethod": "ext-jwt",
    "file": "github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:229",
    "func": "github.com/openziti/ziti/controller/model.(*AuthModuleExtJwt).process",
    "level": "error",
    "msg": "encountered 1 candidate and all failed to validate for primary authentication, see the following log messages",
    "time": "2025-12-16T18:48:43.618Z"
  },
  {
    "authMethod": "ext-jwt",
    "error": "error during authentication policy and identity lookup by claims type [external id] and claim id [astro@mydomain.com]: INVALID_AUTH: The authentication request failed",
    "expectedAudience": "openziti",
    "file": "github.com/openziti/ziti/controller/model/authenticator_mod_ext_jwt.go:84",
    "func": "github.com/openziti/ziti/controller/model.(*AuthTokenVerificationResult).LogResult",
    "issuer": "https://auth.mydomain.com/application/o/openziti/",
    "level": "error",
    "msg": "failed to validate candidate JWT at index 0",
    "time": "2025-12-16T18:48:43.620Z",
    "tokenAudiences": [
      "openziti"
    ],
    "tokenIssuerId": "6K1GTY5jkXySS8Kgd2LMsw",
    "tokenIssuerType": "externalJwtSigner"
  }

Hey thank you !

Yes I’ve set my image version to image.tag=1.8.0-pre3.

And yes, there’s no identity in ZAC named “astro@mydomain.com“, but I taught the “enrollToTokenEnabled“ option in the Authentication → JWT Signers section should create the identities “dynamically” as the release note said:

OIDC/JWT Token-based Enrollment

OpenZiti now supports provisioning identities just-in-time through OIDC/JWT token enrollment. External identity
providers can be configured to allow identities to enroll using JWT tokens, with support for the resulting
identities to use certificate or token authentication.

[...]


enrollToTokenEnabled - When enabled, identities can use a JWT token to enroll. The current token or future tokens
may be used for authentication.

So if i understand correctly I shouldn’t have to create my users identities, they should get created automatically with the IDP (Authentik in my case) no ?

Ah, neat, just-in-time provisioning! Based on a quick review, it looks like you submitted a valid access token to the authentication endpoint, but expected JiT enrollment, which requires submitting the token to the enrollment endpoint instead.

Try this:

  1. POST /enroll/token for just-in-time provisioning (Edge Client API Reference | NetFoundry Documentation)
  2. Verify the identity was provisioned with externalId=<email claim>
  3. AuthN with access token to POST /authenticate?method=ext-jwt

Double-check your ext-jwt-signers enrollAttributeClaimsSelector property. You probably want to configure Authentik to set a claim like "roles" to determine the provisioned identity's role attributes that will match Ziti service policies and edge router policies, and to set this property to the same value as that custom claim.

UPDATE: @astro looks like you're ahead of the pack on this one. :slightly_smiling_face: A future Ziti Desktop for Windows, and tunneler clients generally, should do this for you automatically, eventually, but it's not yet done for that feature that's going to come out in 1.8.0.

Hey, damn I taught it was already out with v1.8.0 ;( !

I see that with the Ziti-Controller I can set the “enrollToTokenEnabled“ via CLI which is in the v1.8.0 changelog (and I’m using that version of the controller since i set image.tag=1.8.0-pre3 in my Helm values).

But is the problem that the Ziti Desktop client for Windows not up to date ? (and so it doesn’t do the POST call to /authenticate?method=ext-jwt ?

Thank you !

I believe the issue is that edge tunnelers don't yet call POST /enroll/token for just-in-time provisioning; they only call the /authenticate endpoint, which requires the identity to exist with a matching externalId.

1 Like

mmm I see damn, I don’t see any branch on the different repos that implemented that just yet… Anyway thanks for the info ! I really taught it was already in place :sob: