# OIDC Auth Paradox: "Identity Not Found" during login, but "Duplicate" when creating it

**URL:** <https://openziti.discourse.group/t/oidc-auth-paradox-identity-not-found-during-login-but-duplicate-when-creating-it/5526>\
**Category:** Uncategorized\
**Created:** [January 27, 2026, 1:52pm UTC](https://openziti.discourse.group/t/oidc-auth-paradox-identity-not-found-during-login-but-duplicate-when-creating-it/5526 "2026-01-27T13:52:04Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![frustrada1234](https://avatars.discourse-cdn.com/v4/letter/f/8edcca/32.png) [@frustrada1234](https://openziti.discourse.group/u/frustrada1234)\
**Post date:** [January 27, 2026, 1:52pm UTC](https://openziti.discourse.group/t/oidc-auth-paradox-identity-not-found-during-login-but-duplicate-when-creating-it/5526/1 "2026-01-27T13:52:04Z")

</div>

Hi everyone,

I’m stuck in a loop trying to set up **Ziti Desktop Edge for Windows (ZDEW)** with **Microsoft Entra ID (Azure AD)** using an **External JWT Signer** and the **Network JWT** method.

## Summary / Paradox

- **During login (ZDEW Authorize):** controller rejects auth with `identity not found by externalId`.

- **During manual creation:** if I try to create the identity with that externalId, the controller rejects it as a **duplicate** (so it already exists).

So it looks like the identity exists in the DB, but the external JWT auth path can’t find it.

## Evidence

### A) Controller log during ZDEW login

After successful Microsoft login, controller logs:

```auto
authMethod="ext-jwt" msg="identity not found by externalId" claim_id="user@company.tld"
authMethod="ext-jwt" msg="failed to validate candidate JWT at index 0" error="INVALID_AUTH: The authentication request failed"

```

### B) CLI says the externalId already exists

When trying to create the identity manually:

```auto
ziti edge create identity user "user-azure" --external-id "user@company.tld" ...

```

I get something like:

```auto
duplicate value 'user@company.tld' in unique index on identities store

```

## Environment / Setup Notes

- ZDEW external provider flow uses the **Network JWT** (my Network JWT payload shows `em: "red"` → not an OTT token).

- Controller + ZAC are running in Docker.

- I’m aware of the Docker advertised address issue (clients must reach the advertised controller hostname). ZDEW is configured to reach the controller at `<controller-fqdn-or-ip>:1280`.

## External JWT Signer

```auto
issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
jwksEndpoint: https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
clientId: <client-id-guid>
audience: <client-id-guid>
claimsProperty: email
useExternalId: true
targetToken: ID
scopes: [api://<client-id-guid>/openziti, email, offline_access, openid, profile]

```

## Token

Decoded ID token contains:

- `iss` = `https://login.microsoftonline.com/<tenant-id>/v2.0` (matches signer)

- `aud` = `<client-id-guid>` (matches signer) **(Please confirm if Ziti strips 'api://' or requires exact string match)**

- `email` = `user@company.tld` (matches the identity externalId)

## Questions

1. In OpenZiti, can `INVALID_AUTH` be thrown for another reason (issuer/aud/signature) but still surface as “identity not found by externalId”? Or is that message strictly from the identity lookup step?

2. Could this happen if ZDEW is authenticating against a different controller instance/advertised address than the one where the identity exists (so CLI shows the identity in one DB but ZDEW hits another)?

3. Is there a recommended way to confirm which externalId the controller actually tries to map (e.g., log level / specific debug flags)?

Any pointers to break this loop would be appreciated. I can share additional sanitized logs (including timestamps and src.remote) if needed.

Thanks!

---

<div class="post-metadata">

**Author:** ![TheLumberjack](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/thelumberjack/32/113_2.png) [@TheLumberjack](https://openziti.discourse.group/u/TheLumberjack)\
**Post date:** [January 27, 2026, 5:40pm UTC](https://openziti.discourse.group/t/oidc-auth-paradox-identity-not-found-during-login-but-duplicate-when-creating-it/5526/2 "2026-01-27T17:40:11Z")

</div>

Hi @frustrada1234, welcome to the community and to OpenZiti!

I'm going to guess you discovered auth policies and chose an auth policy as well for this user? Please reset the auth policy to the default policy if that's the case and try again. It's easy to get that wrong.

Yes invalid auth can be thrown for A TON of reasons. Most common is the issuer not actually matching. believe it or not, the traling slash on the audience is a constant frustration. For example if your token has an issuer of "[https://keycloak.zrok.clint.demo.openziti.org:8446/](https://keycloak.zrok.clint.demo.openziti.org:8446/)" but you leave the slash off the end: "[https://keycloak.zrok.clint.demo.openziti.org:8446](https://keycloak.zrok.clint.demo.openziti.org:8446)" you'll get an error.

No, ziti won't change the audience nor strip the api://. [In the docs](https://netfoundry.io/docs/openziti/guides/external-auth/identity-providers/msentra) we specifially mention api:// being necessary. (I wrote those docs fwiw). That's used when sending the request to entra to make sure you get the right token back.

The controller logs do indicate if the external id is being mapped and fails:

```auto
Jan 27 17:39:27 ip-172-31-47-200 ziti[3885570]: {"authMethod":"ext-jwt","authenticatorId":"","externalId":"clint.dovholuk@netfoundry.io","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":"2026-01-27T17:39:27.421Z"}

```

My bet is the auth policy for now.
