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 3 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.

Signed-off-by: David Gageot <david.gageot@docker.com>

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 No issues found — LGTM! View logs.

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.

4 participants