Skip to content

feat(auth): mint Docker tokens from the stored access token - #3935

Open
dgageot wants to merge 4 commits into
docker:mainfrom
dgageot:new-auth
Open

feat(auth): mint Docker tokens from the stored access token#3935
dgageot wants to merge 4 commits into
docker:mainfrom
dgageot:new-auth

Conversation

@dgageot

@dgageot dgageot commented Aug 6, 2026

Copy link
Copy Markdown
Member

Docker Desktop's backend API only hands out its own access token — valid
for 15 minutes — and never the refresh token behind it, so an expired JWT
cannot be renewed: a stuck refresher on Desktop's side leaves every
caller with the same dead token. The access token docker login (and
Desktop's own sign-in) leaves in the credential store is long-lived, and
Docker Hub exchanges it for a fresh token with no user interaction.

This package owns that exchange: an in-memory token renewed ahead of its
expiry, credential re-checks so a logout or an account switch is noticed,
a shared cache so sibling processes don't each mint their own, retries
for transient failures (honouring Retry-After), a long back-off when the
token is refused, issuer and audience validation, and clock-skew
correction learned from Hub's Date header so expiry decisions follow the
issuer's clock rather than a drifting local one.

The access token itself never leaves the process except in the exchange
request: the endpoint is pinned to a Docker host, redirects are not
followed, and account passwords are never sent. Set
DOCKER_AGENT_NO_TOKEN_EXCHANGE to opt out entirely.

@dgageot
dgageot requested a review from a team as a code owner August 6, 2026 15:55
@aheritier aheritier added area/cli CLI commands, flags, output formatting area/core Core agent runtime, session management area/providers/openai For features/issues/fixes related to the usage of OpenAI models area/providers/anthropic For features/issues/fixes related to the usage of Anthropic models area/providers/gemini Google Gemini provider support kind/feat PR adds a new feature (maps to feat:). Use on PRs only. labels Aug 6, 2026
dgageot added 4 commits August 6, 2026 23:49
Docker Desktop's backend API only hands out its own access token — valid
for 15 minutes — and never the refresh token behind it, so an expired JWT
cannot be renewed: a stuck refresher on Desktop's side leaves every
caller with the same dead token. The access token `docker login` (and
Desktop's own sign-in) leaves in the credential store is long-lived, and
Docker Hub exchanges it for a fresh token with no user interaction.

This package owns that exchange: an in-memory token renewed ahead of its
expiry, credential re-checks so a logout or an account switch is noticed,
a shared cache so sibling processes don't each mint their own, retries
for transient failures (honouring Retry-After), a long back-off when the
token is refused, issuer and audience validation, and clock-skew
correction learned from Hub's Date header so expiry decisions follow the
issuer's clock rather than a drifting local one.

The access token itself never leaves the process except in the exchange
request: the endpoint is pinned to a Docker host, redirects are not
followed, and account passwords are never sent. Set
DOCKER_AGENT_NO_TOKEN_EXCHANGE to opt out entirely.
Gateway clients are rebuilt for every request and each one asks for a
token, so every LLM call used to pay a round-trip to Docker Desktop over
its socket: the token is now kept in memory until it nears its expiry.

A token that is about to expire counts as unusable — it would die
mid-request — and when Desktop has nothing usable to offer, minting from
the stored access token comes before nudging Desktop: it needs nothing
from Desktop and, unlike a forced refresh, is deterministic. A token
Docker refused is never served again, however healthy Desktop believes it
to be.

GetTokenWithSource reports where a token came from, and the signed-in
account is now read from the token's claims when Desktop isn't around to
be asked, so DOCKER_USERNAME and DOCKER_EMAIL also resolve on a plain
`docker login`.
A 401 from the gateway is a more reliable signal than local expiry
arithmetic, which a skewed clock, a stale cache or a revoked session can
all get wrong. Gateway-bound clients now forget the rejected token, ask
for a fresh one and replay the request once — bodies that cannot be
rewound and requests that presented no token are left alone.
`docker agent debug auth` now shows whether the token came from Docker
Desktop or was minted from the stored access token — the first thing to
know when the gateway rejects it. The doctor issue no longer implies
Docker Desktop is the only way to sign in.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/cli CLI commands, flags, output formatting area/core Core agent runtime, session management area/providers/anthropic For features/issues/fixes related to the usage of Anthropic models area/providers/gemini Google Gemini provider support area/providers/openai For features/issues/fixes related to the usage of OpenAI models kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants