Skip to content

Conversation

@wesleymccollam
Copy link
Contributor

@wesleymccollam wesleymccollam commented Oct 30, 2025

Change Description

  • Add support for three OAuth2 authentication flows: authorization_code, client_credentials, and device_code
  • Implement dual token storage: macOS/Linux keychain (primary) with JSON file fallback (~/.pingcli/credentials/)
  • Add --use-keychain flag to login and export commands for storage control
  • Add interactive login flow with automatic token validation and refresh
  • Retain worker app authentication for backward compatibility
  • Update authentication documentation with OAuth2 flow examples
  • Generate AsciiDoc portal documentation for all commands
  • Enhance Makefile with auth-specific test targets

Change Characteristics

  • This PR contains beta functionality
  • This PR requires introduction of breaking changes

Checklist

All full (or complete) PRs that need review prior to merge should have the following box checked.

If contributing a partial or incomplete change (expecting the development team to complete the remaining work) please leave the box unchecked

  • Check to confirm: I have performed a review of my PR against the PR checklist and confirm that:
    • Changes have proper unit and acceptance test coverage (including regression tests)
    • Impacted command, parameter and flag descriptions have been reviewed and updated
    • Impacted command examples have been reviewed and updated
    • Impacted documentation files have been reviewed and updated
    • Does not introduce breaking changes to commands, parameters or flags (unless required to do so)
    • I am aware that changes to generated code may not be merged

Required SDK Upgrades

  • https://github.com/pingidentity/pingone-go-client v0.3.0

Testing

This PR has been tested with:

  • Unit tests (please paste commands and results below)
  • Acceptance tests (please paste commands and results below)
  • End-to-end tests (please paste the link to the actions workflow runs)
  • Not applicable (no evidences needed)

Shell Command(s)

make test-auth

Testing Results

Expand Results
  > Env: Checking PingOne test variables...
echo "✅ Required PingOne test variables are set."
✅ Required PingOne test variables are set.
  > Test: Running auth tests sequentially to avoid shared resource conflicts...
    -> ./internal/auth/...
ok      github.com/pingidentity/pingcli/internal/auth   6.481s
    -> ./cmd/auth/...
ok      github.com/pingidentity/pingcli/cmd/auth        6.062s
✅ All auth tests passed.

End-to-end Tests Workflow Links

  • N/A

wesleymccollam and others added 10 commits September 29, 2025 09:48
- Detect cached vs new tokens by comparing AccessToken before/after SDK call
  in PerformDeviceCodeLogin, PerformAuthCodeLogin, PerformClientCredentialsLogin
- Add GetAuthMethodKeyFromConfig() to extract grant type from Configuration
  instead of reading from config file (fixes incorrect auth method detection)
- Fix device_code nil pointer by always calling WithDeviceCodeScopes()
- Alphabetize PingOne authentication flags in platform export command
- Update test assertions and clean up unused parameters

Login commands now correctly show "Already authenticated" for cached tokens
and "Successfully logged in" only for new authentication.
- Use Cobra flag validation (MarkFlagsMutuallyExclusive/MarkFlagsOneRequired)
- Consolidate duplicate switch statements and applyRegionConfiguration functions
- Validate auth methods against SDK grant type enums
- Use errors.Join() in ClearToken()
- Add error handling in GetAuthMethodKey()
- Remove worker type support and unused test helpers
- Clean up commented code blocks and fix linting issues

All three OAuth2 methods tested and working with proper cache detection.
…authentication

Add comprehensive OAuth2 authentication support with three methods:
- Authorization code flow (auth_code) - now default when no flag specified
- Client credentials flow (client_credentials)
- Device code flow (device_code)

Changes:
- Add auth method flags to login command: --auth-code/-a, --client-credentials/-c, --device-code/-d
- Make auth_code the default authentication method (no flag required)
- Add auth method flags to logout command for targeted credential clearing
- Implement keychain storage per auth method using SDK's GenerateKeychainAccountName
- Replace custom token key hashing with SDK's built-in implementation
- Add comprehensive integration tests for all three authentication flows
- Update unit tests with table-driven approach for better coverage
- Simplify token verification in tests (SDK handles keychain storage internally)
- Token validation: Check cached tokens before re-authenticating (all 3 auth methods)
- Dual storage: System keychain (primary) + ~/.pingcli/credentials/ (fallback)
- --use-keychain flag (default: true) with PINGCLI_AUTH_USE_KEYCHAIN env var
- Per-method token storage using hash-based keys to prevent conflicts
- Logout clears both keychain and file storage
- Updated messages to reflect "storage" instead of "Keychain"
* Update 18 auth tests to handle automatic authentication behavior
* Fix platform export tests to use correct static error definitions
* Add comprehensive test coverage (auth_services_test.go, workflow tests)
* Resolve all linting violations (err113, errorlint, nlreturn, gocritic, staticcheck)
* Use newly implemented Errors package

Enables: pingcli login --auth_code | --client_credentials | --device_code
All 83 tests passing (50 command + 33 auth). UAT verified.
Implements dual storage strategy for OAuth2 tokens to ensure reliability
across all environments (SSH, containers, CI/CD, desktop).

Token Storage:
- Primary: OS credential stores (Keychain/Credential Manager/Secret Service)
- Secondary: File storage at ~/.pingcli/credentials/*.json
- Tokens saved to BOTH locations on authentication
- One file per auth method (device-code-token.json, auth-code-token.json, etc.)

New --use-keychain Flag:
- Controls token retrieval preference (default: true with fallback)
- Added to: auth login, platform export, request commands
- --use-keychain=true: Keychain only (fails if unavailable)
- Default behavior: Try keychain first, fallback to file automatically

Commands Updated:
- platform export: Added --use-keychain and PingOne auth flags
- request: Added --use-keychain and PingOne auth flags

Testing:
- New test-auth Makefile target for sequential auth test execution
- Prevents parallel test resource conflicts
- Refactored list_keys_test.go to table-driven pattern

Documentation:
- Updated README.md with improved installation instructions
- Added comprehensive dual storage documentation
- Updated auth docs (login.md, logout.md) with storage behavior
- Removed redundant overview.md

Code Cleanup:
- Removed deprecated legacy worker token caching
- Removed unused generateLegacyWorkerTokenKey function
- Removed unused crypto/sha256 import
- Removed local go.mod replacement line
@wesleymccollam wesleymccollam requested a review from a team as a code owner October 30, 2025 16:27
var token *oauth2.Token
var newAuth bool

switch selectedMethod {
Copy link
Contributor

Choose a reason for hiding this comment

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

This block below the if !flagProvided block seems to duplicate the logic in performLoginByConfiguredType, we should avoid the repetition

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this logic has been moved to login_internal

pwd, err := os.Getwd()
if err != nil {
return resolvedOutputDir, &errs.PingCLIError{Prefix: exportErrorPrefix, Err: fmt.Errorf("%w: %w", ErrGetPresentWorkingDirectory, err)}
return "", &errs.PingCLIError{Prefix: exportErrorPrefix, Err: fmt.Errorf("%w: %w", ErrGetPresentWorkingDirectory, err)}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these returns changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this should be resolved

}

func initRegionCodeOption() {
func initPingOneAuthenticationWorkerClientIDOption() {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we consider these worker options legacy (based on comments in other files), we should somehow mark them as deprecated here and inform people what the proper args to use are.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added deprecation instructions to usage info

)

// AuthServices represents a list of authentication service names (pingone, pingfederate)
type AuthServices []string
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't seem like these are actually used in login logic - it always uses pingone. I'd leave this code out until we need it in the future, or at least remove "pingfederate" for this commit because selecting it doesn't actually do anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

these are removed

case options.PINGONE_REGION_CODE:
switch typedValue := vValue.(type) {
case *customtypes.PingOneRegionCode:
case *customtypes.String:
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this has been reverted

- refactor redirectURI to include path and port for auth_code flow, update tests
- revert auth-specific parallel tests, run all tests in parallel
- replace --use-keychain flag with --file-storage flag, which disables keychain usage
- remove unused ClearPingOneClientCache method
- correct login_interactive file name
* add storage location type
* update file storage flag
* move login and logout logic to internal package
* add debug message for failed keychain
* remove unneeded auth services
* fix issue where file storage did not occur along with keychain saving in default behavior

if useKeychain == "" {
// If not set, default to true
if useFileStorage == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should use ParseBool to check the value of the --file-storage flag, similar to how other commands do this (for example see the overwrite export option

overwriteExport, err := profiles.GetOptionValue(options.PlatformExportOverwriteOption)
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this should be resolved


storage := getTokenStorage(authMethod)

// Try keychain storage first
Copy link
Contributor

Choose a reason for hiding this comment

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

Does pingcli need to do this if the storage method is keychain? I would expect the CLI to handle keychain storage for us, pingcli only needs to worry about the fallback to file storage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pingcli should be using all SDK methods for keychain usage

func SaveToken(token *oauth2.Token) (StorageLocation, error) {
return SaveTokenForMethod(token, deviceCodeTokenKey)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

The err in LoadToken below is being ignored when not nil

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this should be resolved

* clear any previous token keychain entries or files if previous use of the same grant type was found on login and logout
* use new WithStorageName method
* update package name call for top level domain
* correct context in internal auth method
* use GetOptionValue methods to retrieve auth method values
* remove redundant login and logout logic based on auth type
* return missing configuration error, do not ask user if they'd like to reconfigure
* remove unreliable isMissingConfigError, return authentication failed error instead
* correct some errors to use PingCLI error type
* update logout output message to include where token storage method was removed
* correct login and logout documentation
* improve error handling and logic in credentials.go
* correct comment lines
* remove unused pingfederate and pingone files
* remove unneeded legacy methods
* remove old test-auth references from Makefile
* use ParseBool for file storage option
* remove redundant check for cached token
* require redirecturi port when using device code flow
* store profile name and auth method in json file name, adding ability to clean up and remove past used token files. Added test coverage for this as well.
…code, use one environment id for all grant types
…ction

Add token expiry tracking to distinguish between new authentication and
cached token reuse. Improve user messaging to clearly indicate when tokens
are being refreshed automatically vs. using existing valid tokens.
…in the config, user will receive interactive login experience
… invalid authentication type" after interactive login is complete
… doc, add success output to request DELETE method
…v id when using platform export, correct file storage flag behavior
…v id when using platform export, correct file storage flag behavior, support storage name prefix
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.

3 participants