feat: add verify-login command for headless AuthKit loop verification#190
feat: add verify-login command for headless AuthKit loop verification#190nicknisi wants to merge 1 commit into
Conversation
Add `workos verify-login`, a top-level resource command that proves the AuthKit login loop end-to-end against the active environment with no browser and no email/OTP retrieval, closing the last agent-oriented gap in the friction log (an autonomous agent could not self-verify the post-login flow). The command runs a full round-trip against the bundled @workos-inc/node SDK: createUser (throwaway, emailVerified) -> authenticateWithPassword -> assert access + refresh tokens -> deleteUser in a finally block so cleanup always runs, even on auth failure. A failed cleanup is surfaced machine-readably (orphanedUserId + userCleanedUp:false) rather than swallowed, and does not change the exit code. Two unconditional safety rules (no override flag): production refusal on both env.type === 'production' and an sk_live_ key prefix, before any SDK call; and always-attempt cleanup. Output has human (checklist + verdict) and JSON (flat verdict object) modes driven by the global --json flag and non-TTY auto-detection. Auth-required exits 4 via resolveApiKey; refusal / missing client id emit the standard error envelope and exit 1; a failed verification emits the verdict with success:false and exits 1. The --method magic-auth stretch is not shipped: its gate (empirical staging confirmation that createMagicAuth returns the code in the API response) cannot be exercised in this headless run, so per the spec the flag is dropped and password grant remains the only shipped method. Resolves friction-log goal 10.
| .command( | ||
| 'verify-login', | ||
| 'Verify the AuthKit login loop end-to-end against the active environment (creates and deletes a throwaway user)', | ||
| (yargs) => | ||
| yargs.options({ | ||
| ...insecureStorageOption, | ||
| 'api-key': { | ||
| type: 'string' as const, | ||
| describe: | ||
| 'WorkOS API key (overrides environment config). Format: sk_test_* (production keys are refused)', | ||
| }, | ||
| 'client-id': { | ||
| type: 'string' as const, | ||
| describe: 'WorkOS client ID (overrides the active environment)', | ||
| }, | ||
| method: { | ||
| type: 'string' as const, | ||
| choices: ['password'] as const, | ||
| default: 'password', | ||
| describe: 'Authentication method to verify', | ||
| }, | ||
| }), | ||
| async (argv) => { | ||
| await applyInsecureStorage(argv.insecureStorage as boolean | undefined); | ||
| const { resolveApiKey, resolveApiBaseUrl } = await import('./lib/api-key.js'); | ||
| const { getActiveEnvironment } = await import('./lib/config-store.js'); | ||
| const { runVerifyLogin } = await import('./commands/verify-login.js'); | ||
|
|
||
| const apiKey = resolveApiKey({ apiKey: argv.apiKey as string | undefined }); // exits 4 if none | ||
| const activeEnv = getActiveEnvironment(); | ||
|
|
||
| await runVerifyLogin({ | ||
| apiKey, | ||
| clientId: (argv.clientId as string | undefined) ?? activeEnv?.clientId, | ||
| baseUrl: resolveApiBaseUrl(), | ||
| envType: activeEnv?.type ?? null, | ||
| envName: activeEnv?.name, | ||
| method: argv.method as VerifyLoginMethod, | ||
| }); | ||
| }, | ||
| ) |
There was a problem hiding this comment.
🔍 verify-login is not excluded from the unclaimed-environment warning middleware
The maybeWarnUnclaimed middleware in src/bin.ts:316-332 has an exclusion list for commands like auth, doctor, env, install, etc. The new verify-login command is NOT in this list, meaning it will show the unclaimed-environment warning when applicable. This seems correct — verify-login is a management command that needs a properly configured (claimed) environment to function, so the warning is useful. However, if the intent is for verify-login to work against unclaimed environments (since it has its own envType handling), the warning could be confusing noise.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Closing in favor of a single combined PR from nicknisi/akshay. |
What
workos verify-loginproves the AuthKit login loop works end-to-end without a browser: it creates a throwaway user, authenticates via the SDK password grant (authenticateWithPassword), asserts the returned tokens, and deletes the user in afinallyblock so cleanup happens even on failure. Human and--jsonoutput; exit codes follow the CLI'sgh-style convention.Production refusal is unconditional: the command refuses to run when the active environment is production-type or the API key has the
sk_live_prefix, checked before any SDK call. There is deliberately no override flag — the command creates and deletes real users.Why
From the friction log (Part 2, full-agent run): the agent could scaffold the whole integration but had no way to verify the post-login flow headlessly, so "did login actually work?" required a human with a browser. The SDK password grant makes this buildable today with zero backend work.
Testing
verify-login.spec.ts(mocked SDK): happy path, token assertion failure, cleanup-on-failure, production refusal both ways, JSON mode.Stack 6/7 (
akshay-friction-log): merge after #189, then rebase ontomain.