feat(webauth): honor a CLI-requested token lifetime#938
Open
tomaskir wants to merge 1 commit into
Open
Conversation
The CLI webauth login flow always minted non-expiring PATs because the
approval page hardcoded the token expiry to null. Let the CLI request a
token lifetime in the webauth payload and mint the PAT with that expiry.
- Add parseWebAuthRequest: accept a base64(JSON) payload
{ port, publicKey, name, lifetime? } where lifetime is in seconds, with
a fallback to the legacy port-pubKeyHex-patName format. The legacy parse
also stops truncating token names that contain hyphens.
- Add expiryFromLifetime to convert the requested lifetime (seconds) into
an absolute millisecond expiry passed to generateUserToken.
- Show the resulting token expiry on the approval screen.
- No requested lifetime keeps the current never-expires behavior.
The backend CreateUserTokenMutation already supports per-token expiry, so
no backend change is needed. CLI side tracked in phasehq/cli#302.
Closes phasehq#928
1 task
tomaskir
added a commit
to tomaskir/cli
that referenced
this pull request
Jun 28, 2026
phase auth (webauth mode) always minted a non-expiring PAT named
username@hostname, with no way to override either. Add two optional flags
and send the values to the Console in the webauth request payload:
- --token-name sets the PAT name (default username@hostname). This fixes
unhelpful names under Docker, where the default becomes root@<container-id>.
- --token-lifetime sets the PAT lifetime, e.g. 7d, 12h, 30m, 60s, 2w
(default: never expires).
The webauth payload moves from the hyphen-joined port-pubKeyHex-patName
string to base64(JSON) { port, publicKey, name, lifetime? }, where lifetime
is in seconds. This is parse-safe for names containing hyphens or other
characters and matches the Console webauth page contract.
Add util.ParseTokenLifetime to convert a lifetime string into seconds.
Console side: phasehq/console#928, phasehq/console#937 (PR phasehq/console#938).
Closes phasehq#302
Closes phasehq#303
Contributor
Author
|
Coordinated CLI side: phasehq/cli#304 (adds the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
PATs minted through the CLI
webauthlogin flow always had infinite lifetime. The approval page hardcoded the token expiry tonullwhen callinggenerateUserToken(...), even though per-token expiry is supported everywhere else:CreateUserTokenMutationacceptsexpiry: BigInt(backend/backend/graphene/mutations/environment.py)UserToken.expires_atexists and is nullable (backend/api/models.py)backend/api/utils/rest.py,token_is_expired_or_deleted)The webauth path was the only token-creation flow that could not set an expiry. Fixing it requires reworking the webauth request payload to be parse-safe, which is the same migration two CLI flags depend on (
--token-lifetimeand--token-name). This PR is the Console half; the CLI side is phasehq/cli#302 and phasehq/cli#303.Proposed Changes
Rework the webauth payload parsing to a parse-safe format and honor a CLI-requested token lifetime. No backend change is needed.
frontend/utils/webAuth.ts:parseWebAuthRequest(decoded)accepts a base64(JSON) payload{ port, publicKey, name, lifetime? }:nameis the requested token name, parsed as a string so arbitrary, user-supplied names (hyphens, spaces, etc.) survive intact - this is the format dependency for the CLI--token-nameflag (cli#303).lifetimeis the requested lifetime in seconds (omitted / null / non-positive = never expires) - drives the CLI--token-lifetimeflag (cli#302).port-pubKeyHex-patNameformat for older CLIs. The legacy parse now keeps everything after the second hyphen as the name (params.slice(2).join('-')) instead ofparams[2], which fixes the truncation bug (Webauth PAT names are truncated at the first hyphen #937).expiryFromLifetime(lifetime)converts the requested lifetime (seconds) into an absolute millisecond expiry timestamp, as expected bygenerateUserToken/CreateUserTokenMutation.frontend/utils/tokens.ts: addhumanReadableExpiryTimestamp(expiry)to render an absolute-timestamp expiry, mirroring the existinghumanReadableExpiry.frontend/app/webauth/[requestCode]/page.tsx: useparseWebAuthRequest; compute the expiry at mint time and pass it togenerateUserTokeninstead of hardcodednull; show the resulting expiry on the approval screen. Removed the inlineWebAuthRequestParamsinterface andgetWebAuthRequestParamshelper (moved to the util).No requested lifetime preserves the current never-expires behavior; no requested name preserves the CLI's
username@hostnamedefault.Cross-repo contract: the base64(JSON) payload shape documented in
webAuth.tsis what the CLI side (cli#302, cli#303) must emit. Adding this one format migration unblocks both CLI flags.Screenshots or Demo
Visible change: on the CLI authentication ("in progress") screen, a line now shows the resulting token expiry, e.g. "This token will expire on 7/5/2026." or "This token will never expire." when no lifetime was requested.
Release Notes
The CLI login flow (
phase authwebauth) can now request a token lifetime, and the Console honors it - the minted personal access token expires accordingly instead of always living forever. Logins that do not request a lifetime are unchanged (non-expiring). The webauth payload is also now parse-safe, which fixes PAT names being truncated at the first hyphen and lets the CLI send a custom token name. Requires a CLI version that sends the new payload (phasehq/cli#302, phasehq/cli#303); older CLIs keep working unchanged.Open Questions
lifetimein seconds (Console computes the absolute expiry) andnameas a free-form string. This needs to match feat: allow requesting a token with a specific lifetime during phase auth cli#302 and feat: allow setting the token name during phase auth cli#303. Happy to adjust field names/units if preferred.Testing
frontend/tests/utils/webAuth.test.ts(new):parseWebAuthRequestacross JSON and legacy payloads, lifetime present/absent/non-positive, hyphenated token names (the Webauth PAT names are truncated at the first hyphen #937 case), and the non-object-JSON fallback guard; plusexpiryFromLifetime(null, zero, future timestamp).frontend/tests/utils/tokens.test.ts(new):humanReadableExpiryTimestampnull and timestamp branches.yarn test tests/utils/webAuth.test.ts tests/utils/tokens.test.ts). Lint andtsc --noEmitclean for the changed files, andnext buildsucceeds (/webauth/[requestCode]compiles).GET /webauth/<base64(JSON)>serves and redirects unauthenticated users to/loginwith the payload preserved intact.page.tsxwiring is not unit-tested - this repo has no page/component test pattern (jest.config.jsroots totests/, which holds pure-util tests), so the logic was extracted into testable utils instead. Full end-to-end verification (login -> approve -> mint) needs a running CLI + Console.Reviewer Focus
Start at
frontend/utils/webAuth.ts-parseWebAuthRequest(the payload contract, legacy fallback, and arbitrary-name handling) is the core of the change and the cross-repo interface shared by both CLI flags. Thenfrontend/app/webauth/[requestCode]/page.tsxfor how the expiry is computed and passed togenerateUserToken.Additional Context
--token-lifetime) and feat: allow setting the token name during phase auth cli#303 (--token-name), which share this payload-format migrationHow to Test the Changes Locally
docker compose --env-file .env.dev -f dev-docker-compose.yml up -d) and sign in.phase auth --token-lifetime 7dand complete the webauth flow; confirm the approval screen shows the expiry and the created PAT carriesexpires_at.phase auth --token-name "ci-prod-api"and confirm the PAT is created with that exact name (including hyphens).phase authwebauth login and confirm the token is still created, never-expiring, and itsusername@hostnamename is no longer truncated at the first hyphen.{"port":8002,"publicKey":"<hex>","name":"user@my-host","lifetime":604800}and open/webauth/<b64>; verify the full name is used and the expiry line renders.Did You...
next buildsucceeds;/webauth/[requestCode]compiles