Skip to content

@W-20893868 feat(auth): migrate browser login to Authorization Code + PKCE#574

Draft
clavery wants to merge 11 commits into
mainfrom
feature/pkce-support
Draft

@W-20893868 feat(auth): migrate browser login to Authorization Code + PKCE#574
clavery wants to merge 11 commits into
mainfrom
feature/pkce-support

Conversation

@clavery

@clavery clavery commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Migrates the default browser-based OAuth flow from the deprecated implicit grant to Authorization Code + PKCE (S256), per OAuth 2.1. The user auth method now uses PKCE by default, persists a refresh token, and hardens the on-disk session store. A transitional fallback keeps users on not-yet-migrated clients working during the rollout.

What changed

  • PKCE by default. b2c auth login and the user auth method use Authorization Code + PKCE and persist a refresh token, so subsequent commands refresh silently without reopening the browser. Default auth-method chain is now client-credentials, jwt, user.
  • Implicit still selectable via --auth-methods implicit (or dw.json) for backward compatibility, with a deprecation warning; it cannot obtain refresh tokens.
  • user-auth shorthand in dw.json ("user-auth": true = "auth-methods": ["user"]), and a --auth-methods flag on auth login. The POC auth pkce command is removed.
  • Session hardening. Persisted browser-auth sessions (now holding long-lived refresh tokens) are written 0o600 in a 0o700 directory; a stale token refreshes silently instead of reopening the browser.
  • pod5 default client swapped to a PKCE-capable public client.
  • Transitional PKCE→implicit fallback. If the configured client is not a PKCE-capable public client, the user flow falls back to implicit and warns. The fallback triggers only on OAuth errors that genuinely indicate the client can't do the code grant — invalid_client, unauthorized_client, unsupported_response_type, unsupported_grant_type. Other errors (invalid_scope, access_denied, invalid_grant, …) surface directly, since they would fail identically under implicit. Disable with SFCC_DISABLE_PKCE_FALLBACK=1.
  • Docs. New "Implicit Flow Deprecation (PKCE Migration)" section in the authentication guide; deprecation messages link to it and recommend creating a new public client (an AM client's type can't be changed after creation).
  • VS Code extension persists PKCE refresh tokens via OS-keychain-backed SecretStorage.

Manual testing

A companion smoke-test harness is included (SMOKE-TEST.md + scripts/pkce-smoke-test.sh, not shipped). Run it against a real Account Manager / instance to validate:

  • PKCE happy pathb2c auth login <public-client> opens the browser, completes via code_challenge_method=S256, saves a session with a refresh token; no deprecation warning.
  • Silent refresh — after login, force-expire the stored access token (keeping the refresh token) and re-run auth token; expect [Auth] PKCE token refreshed silently and a rotated token, no browser.
  • Fallback triggers correctly — a legacy implicit-only client (invalid_client at token exchange) warns and falls back to implicit; a working PKCE client with a bad scope (invalid_scope) surfaces the error directly with no fallback and no browser re-attempt.
  • Default-client use cases (no --client-id) — am users list, sandbox realm list, slas client list --tenant-id … --short-code … all authenticate via the default public client + PKCE.
  • Instance operationscode list, bm whoami, webdav ls, a webdav put→ls→rm round-trip, and job export all succeed with a user/PKCE token (WebDAV and OCAPI both), plus client-credentials and Basic-auth regression paths.
  • Legacy flows still work — explicit implicit, client-credentials (store + read), JWT Bearer, logout.

Full scenario reference and a menu-driven runner are in SMOKE-TEST.md.

clavery added 10 commits May 12, 2026 21:32
Adds Authorization Code + PKCE as the default user-auth flow, replacing
the OAuth implicit flow in the default chain. New AuthMethod 'user' maps
from --user-auth.

Unifies the per-flow session caches into a single pluggable
AuthSessionStore (file-backed by default, VS Code SecretStorage in the
extension). Sessions are tagged with flow ('pkce' | 'implicit' |
'client-credentials') to discriminate refresh policy.

- PKCE: persists access + refresh tokens; refreshes silently across
  invocations, falling back to the browser flow only if refresh fails.
- Implicit: persists access token; emits a deprecation WARN suggesting a
  public client + --user-auth.
- Client-credentials (auth client): persists access token only. Client
  secret is NEVER persisted. --renew flag and `auth client renew`
  command removed accordingly.

Other:
- New default public client a40a7a9b-... for PKCE; legacy implicit
  client retained as LEGACY_IMPLICIT_PUBLIC_CLIENT_ID.
- OAuth client-id mismatch protection at config merge: drops a base
  clientSecret when override clientId differs (mirrors hostname mismatch
  protection).
- Test coverage: oauth-pkce.test.ts mirrors implicit tests plus refresh,
  hydration, and persist; session-store.test.ts; oauth-command tests
  for the implicit deprecation WARN; auth login command test.
- Docs updated for new auth flow, removed --renew references.
Covers hydrate/find/save/delete/list/clearAll against fake SecretStorage
+ Memento. Verifies sync-snapshot reads, async write-through, replacement
of prior records by clientId, and graceful handling of corrupted JSON.
…ce command

- dw.json gains "user-auth": true shorthand for "auth-methods": ["user"]
  (mutually exclusive with auth-methods; rejected during config mapping)
- b2c auth login gains --auth-methods flag to opt into legacy implicit flow
  (emits deprecation warning)
- remove POC b2c auth pkce command
- add changeset and update auth/config docs
# Conflicts:
#	docs/cli/auth.md
#	packages/b2c-tooling-sdk/src/auth/stateful-oauth-strategy.ts
#	packages/b2c-vs-extension/src/extension.ts
…est harness

Land the Authorization Code + PKCE swap for browser-based OAuth:
- PkceWithImplicitFallbackStrategy transitional fallback (gated by
  SFCC_DISABLE_PKCE_FALLBACK), wired into resolve/oauth-command/login.
- Session-file hardening (0o600 file / 0o700 dir) and H4 fix
  (invalidateToken preserves the refresh token so stale tokens refresh
  silently instead of reopening the browser).
- Swap the pod5 default public client to the PKCE-capable
  3f41a930-b2bb-42c9-907d-f06a33c85849 (was implicit-only c44527fe...).
- Docs + changeset updated for the new default chain and public-client
  registration guidance.

Add a manual smoke-test harness (not shipped):
- SMOKE-TEST.md reference plan and scripts/pkce-smoke-test.sh, an
  interactive, session-isolated runner covering new (PKCE, fallback,
  hardening, forced refresh_token grant) and legacy (implicit,
  client-credentials, JWT, default-client) flows, plus WebDAV/OCAPI
  instance operations.
…rors

The transitional PKCE→implicit fallback was triggering on ANY OAuth 4xx,
which was wrong in both directions:
- invalid_scope (bad scopes on a working PKCE client) incorrectly fell
  back and told the user to re-register a fine client.
- access_denied (user cancel) at the authorize step would have fallen
  back, contradicting the intended "user cancel must not fall back" rule.

Fallback now fires ONLY on OAuth errors that genuinely indicate the client
is not a PKCE-capable public client: invalid_client (Account Manager
demands client authentication at the token exchange — the common case for
legacy implicit-only clients), unauthorized_client,
unsupported_response_type, unsupported_grant_type. Everything else
(invalid_scope, access_denied, invalid_grant, non-JSON bodies) surfaces
directly, since it would fail identically under implicit.

Also:
- Deprecation messages no longer say "re-register this client" (an AM
  client's type can't be changed); they recommend creating a NEW public
  client and link to a dedicated docs section.
- Add "Implicit Flow Deprecation (PKCE Migration)" guide section
  (guide/authentication.md) documenting the migration, the create-new
  constraint, and the exact fallback trigger rules.
- Update stale b2c-dx.salesforce.com deprecation URLs to the canonical
  docs host.
- Smoke-test harness: add default-client use cases (sandbox realm list,
  slas client list) and a forced-refresh check; refine fallback docs.
# Conflicts:
#	packages/b2c-cli/src/commands/auth/client/index.ts
#	packages/b2c-cli/src/commands/auth/login.ts
#	packages/b2c-tooling-sdk/src/cli/base-command.ts
# Conflicts:
#	docs/guide/authentication.md
#	packages/b2c-tooling-sdk/src/cli/oauth-command.ts
#	packages/b2c-tooling-sdk/test/cli/oauth-command.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant