Ever since we upgraded to Keycloak v26, we get an error message where Standard Token Exchange is not possible unless the Keycloak client is using Client authentication. Because of this, our Ziti sessions would drop after the initial access token lifespan expired (which I had set to 15 minutes). To solve this, I have set my Access token lifespan 8 hours for the time being
After 15 minutes we would get these logs on Keycloak and Ziti Windows logs
Keycloak Logs
TOKEN_EXCHANGE_ERROR reason Standard token exchange is not enabled for the requested client auth_method token_exchange grant_type urn:ietf:params:oauth:grant-type:token-exchange client_auth_method client-secret error invalid_request
Ziti Logs
refresh_cb() OIDC token refresh failed: 400[{ "error": "invalid_request", "error_description": "Invalid token type, must be access token" }]
If I want to enable standard token exchange on the client I need to have Client Authentication configured, which is not an option in the ziti console as far as I can tell?
I only use my keycloak instance in short bursts usually however I do have a keycloak 26 instance setup with defaults. I connected my tunneler and I'll let it sit for "a while" (over 30 minutes now) to see if it ends up losing a session. So far it's been connected and continues to work properly and I have no TOKEN_EXCHANGE_ERROR logs. My setup also uses an Access token.
Currently tokens are set to what I think are the defaults:
ID token: 5 minutes (300s)
Access token: 5 minutes (300s)
Refresh token: 30 minutes (1800s)
Is it possible the client is going to sleep for longer than that refresh token duration and not able to obtain a new access token?
Can you grep your logs for "oidc.c" like i have here? One thing I noticed is the refresh window changes from "well before the token limit" to "sorta close to the limit". It seems possible that if that refresh fails, it might cause this issue. From my logs, notice how the
[2025-10-02T11:25:46.922Z] DEBUG ziti-sdk:oidc.c:966 oidc_client_set_tokens() scheduling token refresh in 300 seconds
[2025-10-02T11:51:58.964Z] DEBUG ziti-sdk:oidc.c:966 oidc_client_set_tokens() scheduling token refresh in 1799 seconds
[2025-10-02T11:51:58.964Z] DEBUG ziti-sdk:oidc.c:966 oidc_client_set_tokens() scheduling token refresh in 1799 seconds
That 1799 seconds is (imo) dangerously close to the 30m refresh token window. It's so close it feels unintentional to me. I'm going to have @ekoby have a look at this to see if he agrees.
Also, weirdly enough I can still access internal resources even though my token has expired. I am not sure if this is new behavior however in the past I would get locked out.
The Ziti Tunneller shows that I need to login again after the 300 seconds.
(disclaimer: I am a Ziti noob and we should wait to let the experts confirm/deny this thought) but I believe the Ziti client will attempt token exchange with a refresh token rather than an access token. I think in theory the RFC does allow refresh_token as subject type but I’m not sure how common this is in practice. I believe I ran into something similar while working on a very simple custom IDP. If the IDP can’t be configured to allow refresh token as subject_token_type during token-exchange than you may need to disable that grant type at your IDP to prevent the Ziti client from trying it and instead fall back to standard refresh_token grant type. Again, not 100% of this, just speculating.
REQUIRED. This parameter is the type of the token passed in the subject_token parameter. This must be urn:ietf:params:oauth:token-type:access_token when the standard token exchange is being used because Keycloak does not support other types for the standard token exchange.
I have had similar experiences. I'm trying to narrow down how and when it happens. Our internal chat tool is a self-hosted mattermost. I setup ext-jwt-auth to that identity using Auth0. I have my own test network where I have http-based resources that uses my own self-hosted keycloak 26.
The mattermost network is solid and doesn't end up having the same behavior you are noticing. It was connected all day. The keycloak instance seems to fail after five minutes for me too by the UI informing me that I need to auth, but the app itself is happy and I'm still actually connected to my http-based resource. The only thing that comes to mind is that the websocket/persistent connection mattermost has somehow makes a difference or the Auth0 tokens somehow are different. There's a lot to debug so it'll probably take a bit to get down to paydirt here. Also during the debugging I noticed that 300/1799 token refresh.
I'm going to keep looking into it as I can. If you can narrow anything down on your side that would be useful, maybe stand up a keycloak25 at least to rule out (or confirm) the upgrade causing any issue? My keycloak is also 26 now so it's possible that it's somehow keycloak related. We have made an update to obtain a new access token sooner than 1s before expiration. It could be that one second was just too close and causing issues too.
I'll keep coming back to this as I can, it certainly seems like some kind of issue to me.
@blup66 yes, if a refresh token is returned during the PKCE flow, that refresh token will be used to refresh an access token at the provided interval. If no refresh token is supplied ziti will force you to re-auth via the idp to get a new access token as that's all we can do.