Skip to content

feat(auth): support OAuth Bearer login alongside PAT#158

Merged
aprimakina merged 3 commits into
mainfrom
feat/oauth-bearer
Jun 9, 2026
Merged

feat(auth): support OAuth Bearer login alongside PAT#158
aprimakina merged 3 commits into
mainfrom
feat/oauth-bearer

Conversation

@aprimakina

@aprimakina aprimakina commented May 20, 2026

Copy link
Copy Markdown
Contributor

Add OAuth (PKCE) authentication to the CLI and consolidate how auth info is
fetched, while keeping PAT / API-key auth fully supported.

Changes

  • OAuth login: tiger auth login now defaults to an interactive browser flow. PAT auth still works via --public-key/--secret-key flags or TIGER_PUBLIC_KEY / TIGER_SECRET_KEY.
  • Bearer client with auto-refresh: OAuth sessions use a Bearer-authenticated client that transparently refreshes expired access tokens and persists the rotated token.
  • auth logout: revokes the refresh token server-side for OAuth sessions.
  • Removed the unused GraphQL-based PAT-creation path.

@aprimakina aprimakina force-pushed the feat/oauth-bearer branch from a2d5415 to 74fdcb8 Compare May 20, 2026 13:38
@aprimakina aprimakina self-assigned this May 20, 2026
@aprimakina aprimakina force-pushed the feat/oauth-bearer branch from 74fdcb8 to 04e3662 Compare May 20, 2026 16:02
@aprimakina aprimakina force-pushed the feat/oauth-bearer branch 3 times, most recently from e36be09 to 493a621 Compare June 3, 2026 14:02
@aprimakina aprimakina marked this pull request as ready for review June 3, 2026 14:06
@aprimakina aprimakina force-pushed the feat/oauth-bearer branch from 493a621 to caba91e Compare June 5, 2026 11:30

@Askir Askir 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.

I think it looks good generally but maybe fix the analytics values before we merge :D

Comment on lines +124 to +126
analytics.Property("userId", apiKey.IssuingUser.Id),
analytics.Property("email", string(apiKey.IssuingUser.Email)),
analytics.Property("planType", apiKey.Project.PlanType),

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.

Do we ever set these in the new oAuth based flow? Otherwise our analytics events will be lacking these details for oauth users, which would be a bit sad and break nice dashboards.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great catch, we didn't. Identify was only being called from ValidateAPIKey, which is the PAT-only path. Should be fixed with the next commit, except for plan_type. Do you think we need it? I haven't noticed any plan-segmented dashboards under https://us.posthog.com/project/104050/dashboard/636540

@aprimakina aprimakina Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread internal/tiger/api/client_util.go Outdated
Comment on lines +129 to +145
// SetTokenExpiry populates Token.Expiry from the gateway's non-standard
// `expires_at` (Unix seconds) field. The Go oauth2 library only reads the
// standard `expires_in`, so without this the stored token's Expiry is zero
// and TokenSource.Valid() always returns true.
func SetTokenExpiry(token *oauth2.Token) {
switch v := token.Extra("expires_at").(type) {
case float64:
if v > 0 {
token.Expiry = time.Unix(int64(v), 0)
}
case int64:
if v > 0 {
token.Expiry = time.Unix(v, 0)
}
}
}

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.

Any thoughts about changing this (or atleast adding the expires_in field) on the backend instead if this is the oauth standard? :D

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread internal/tiger/api/client_util.go Outdated
Comment thread internal/tiger/cmd/oauth.go Outdated
Comment thread internal/tiger/config/credentials.go
Comment thread internal/tiger/common/client.go Outdated
Comment on lines +30 to +39
func defaultGetStoredCredentials() (*config.Credentials, error) {
// Honor a PAT-shaped test override when present; production leaves it nil.
if GetCredentials != nil {
if apiKey, projectID, err := GetCredentials(); err == nil && apiKey != "" {
return &config.Credentials{APIKey: apiKey, ProjectID: projectID}, nil
}
}
return config.GetStoredCredentials()
}

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.

It'd be nice if we could clean this up at least in a follow up and change all the tests to override the correct method with maybe even an oauth token.

@aprimakina aprimakina Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

return projects[0].ID, nil
return projects[0].Id, nil
default:
return selectProjectInteractively(projects, l.out)

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.

This is the only way we allow setting a new projectId right? In theory with oauth you could change projects without having to go through the login command again. Not sure if it's worth allowing this via an extra command, a cli ENV var or a new flag on each command e.g. --projectId? Fine for now but maybe a convenience thing especially as we think about multi project orgs. @nathanjcochran any opinion here?

@nathanjcochran nathanjcochran Jun 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah, imo, we should probably have a command for switching projects (e.g. tiger project switch?), which shows this same interactive menu by default (or takes a project ID as an argument), and stores the user's choice in the keyring, like it does now when you log in. That way, users could switch projects without logging in again (assuming they authenticated with OAuth - the command should probably show an error/warning if you try to run it with API key auth). Could also support other tiger project sub-commands in the future, like tiger project list.

I think we could also bring back the global --project-id flag (and a corresponding TIGER_PROJECT_ID env var), which could be used to override the saved project ID on a per-command basis without changing what's saved in the keyring. Note that the --project-id flag and TIGER_PROJECT_ID env var were part of the original CLI design, but were removed in this PR because we couldn't support switching projects while using API keys/client credentials for auth (which are inherently project-scoped).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@aprimakina aprimakina force-pushed the feat/oauth-bearer branch from ace5dda to d3d1430 Compare June 8, 2026 13:47
- Identify OAuth users for analytics on login, mirroring the PAT path's
  ValidateAPIKey. planType is omitted until the gateway returns it on the
  oauth /auth/info branch.
- Build the token-authenticated client once in loginWithOAuth and reuse it
  for project selection and analytics identification (was constructed twice).
- Route OAuth project-list errors through common.ExitWithErrorFromStatusCode
  so backend messages and exit codes surface in the CLI.
- Reuse the pooled getHTTPClient in the OAuth client so Bearer requests and
  token refreshes share its connection limits.
- Export analytics.Enabled() to skip the extra /auth/info round-trip when
  analytics is disabled.
- Drop the deprecated credential test seam: GetStoredCredentials is now the
  single accessor. Migrate test sites to mockTestPAT/mockNotLoggedIn helpers,
  remove config.GetCredentials, and add OAuth-credential coverage asserting
  the client sends a Bearer token.
- Restore doc comments dropped during the initial refactor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aprimakina aprimakina force-pushed the feat/oauth-bearer branch from d3d1430 to 030d9f0 Compare June 8, 2026 14:24
@aprimakina aprimakina merged commit d204f6e into main Jun 9, 2026
2 checks passed
@aprimakina aprimakina deleted the feat/oauth-bearer branch June 9, 2026 09:47
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