Skip to content

feat(auth): add TokenFetcher interface and token abstraction#1181

Open
TerryHowe wants to merge 5 commits into
oras-project:mainfrom
TerryHowe:feat/auth-token-fetcher
Open

feat(auth): add TokenFetcher interface and token abstraction#1181
TerryHowe wants to merge 5 commits into
oras-project:mainfrom
TerryHowe:feat/auth-token-fetcher

Conversation

@TerryHowe

@TerryHowe TerryHowe commented May 23, 2026

Copy link
Copy Markdown
Member

Summary

Part 2 of 3 splitting #1141 into reviewable chunks.

Extract bearer-token acquisition from auth.Client into a pluggable TokenFetcher interface:

FetchToken(ctx, TokenParams, Credential) (string, error)

Three concrete implementations land in the same file:

Type Purpose
DistributionTokenFetcher GET against the distribution-spec token endpoint with optional basic auth
OAuth2TokenFetcher POST OAuth2 password / refresh_token grant
CompositeTokenFetcher Selects between the above based on credential shape and a LegacyMode toggle — mirrors the existing in-client logic

A new optional Client.TokenFetcher field lets callers inject a custom strategy (e.g. ECR/GCR-specific token exchange) without forking the client. When nil, the built-in fallback in fetchBearerToken preserves the existing behavior, so this is a purely additive change to the public API. ForceAttemptOAuth2 is unchanged.

Why split it out

Today the bearer-token strategy is hardcoded inside Client.fetchBearerToken. Callers who need a custom strategy have to fork or wrap the whole client. With TokenFetcher, they swap in one field.

The composite implementation is also easier to test in isolation than the inline logic — see token_test.go (805 lines) which exercises the strategies without spinning up an HTTP client at all.

This will replace https://github.com/oras-project/oras-go/blob/main/registry/remote/auth/client.go#L266:L400

Test plan

  • go test -mod=mod ./registry/remote/auth/... — passes (existing tests + new TokenFetcher coverage)
  • token_test.go covers all three fetcher implementations independently
  • client_test.go adds TestClient_Do_Bearer_CustomTokenFetcher and _Error to verify wiring
  • CI on this PR

Follow-ups in the split

Replaces #1141 together with PRs A and C.

@codecov

codecov Bot commented May 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.13043% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.19%. Comparing base (2b84d02) to head (2dd7136).

Files with missing lines Patch % Lines
registry/remote/auth/token.go 88.23% 5 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1181      +/-   ##
==========================================
+ Coverage   83.09%   83.19%   +0.09%     
==========================================
  Files          82       83       +1     
  Lines        5834     5926      +92     
==========================================
+ Hits         4848     4930      +82     
- Misses        605      610       +5     
- Partials      381      386       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Extract bearer-token acquisition from auth.Client into a pluggable
TokenFetcher interface:

  FetchToken(ctx, TokenParams, Credential) (string, error)

Three implementations land alongside the interface:

  - DistributionTokenFetcher: GET against the distribution-spec token
    endpoint with optional basic auth.
  - OAuth2TokenFetcher:       POST OAuth2 password/refresh_token grant.
  - CompositeTokenFetcher:    selects between the above based on
                              credential shape and a LegacyMode toggle,
                              mirroring the existing in-client logic.

A new optional Client.TokenFetcher field lets callers inject a custom
strategy (e.g. ECR/GCR-specific token exchange) without forking the
client. When nil, the built-in fallback in fetchBearerToken preserves
the existing behavior, so this is a purely additive change to the
public API. ForceAttemptOAuth2 is unchanged.

Test coverage in token_test.go is independent of the client; two new
client_test.go cases (TestClient_Do_Bearer_CustomTokenFetcher and
_Error) exercise the wiring end-to-end.

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
@TerryHowe
TerryHowe force-pushed the feat/auth-token-fetcher branch from 16542eb to 38f90d6 Compare June 22, 2026 14:35
Comment thread registry/remote/auth/token.go
Comment thread registry/remote/auth/token.go Outdated

@gjenkins8 gjenkins8 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

Perhaps a common HTTP client that does the work of DistributionTokenFetcher.send and OAuth2TokenFetcher.send could be introduced (and would establish common baseline for HTTP request sending)

TerryHowe and others added 2 commits June 26, 2026 18:50
Both DistributionTokenFetcher and OAuth2TokenFetcher carried byte-for-byte
identical send methods and near-identical token-response parsing. Pull the
header/client send logic into sendRequest and the JSON decode into
decodeTokenResponse so each fetcher delegates instead of duplicating.

The unified decoder also accepts a 'token' field as a fallback for the OAuth2
fetcher (previously access_token only); this is within spec and strictly more
lenient.

Addresses review feedback from @gjenkins8 on oras-project#1181.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Terry Howe <terrylhowe@gmail.com>

@gjenkins8 gjenkins8 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(Re) LGTM

In particular, the TokenFetcher interface w/ TokenParams representing the concrete registry details enables evolving how auth tokens are retrieved over time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

keep open v3 Things belongs to version 3.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants