# keycloak invalid_token -  token expired before skew allowance

## TL;DR

The token expired just before the allowed clock-skew window, so Keycloak rejected it. The real fixes are longer-lived tokens where appropriate, refreshing before expiry, and keeping clocks in sync. Chasing the skew allowance itself is treating the symptom.

```text
keycloak: invalid_token - token expired before skew allowance
```

## Steps

1. Check the token's exp claim vs the rejection time: how far past expiry was it.
   Expected: The gap is measured.

2. Sync clocks between the client, Keycloak, and the resource server.
   Expected: Skew is minimized.

3. Refresh tokens before expiry: request the new token at 80% of lifetime, not after 401s.
   Expected: Tokens never arrive expired.

4. Review the token lifetime for the client: if it is too short for the use case, extend it deliberately.
   Expected: The lifetime fits the flow.

5. Keep the skew allowance at its default; widening it weakens replay protection.
   Expected: The security property holds.

## When to use

- Keycloak rejects tokens at expiry boundaries
- clocks may differ between systems
- short-lived tokens in slow flows

## When not to use

- tokens are long-expired (that is not a skew problem)
- the token was never valid (check the signature and issuer)
- the client clock is wildly off (fix the clock, not the allowance)

## Tool compatibility

- Keycloak
- OAuth2/OIDC

## Variant phrasings

- "keycloak invalid_token token expired"
- "keycloak skew allowance expired"
- "keycloak token expired before skew"

## Why it happens

Token validation allows a small clock-skew grace, but a token that expired beyond the grace is simply expired. Systems with slow flows or drifting clocks hit the boundary constantly. The durable fix is timely refresh and synced clocks; the skew allowance is a tolerance, not a lifetime extension.

## Edge cases

- do not widen the skew allowance as a fix; it weakens security for every client
- the 80%-lifetime refresh rule needs the client to know the lifetime; read it from the token
- service-to-service flows need their own refresh discipline; they cannot prompt a user

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_qt6yAzqJvodYinNdqdO4Cg
