{"record":{"id":"907b143c7e696263","repo":"ruvnet/ruflo","slug":"authorization-was-denied-or-failed-detail","errorCode":null,"errorMessage":"authorization was denied or failed: ${detail}","messagePattern":"authorization was denied or failed: (.+?)","errorType":"exception","errorClass":"LoginDeniedError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/auth/client.ts","lineNumber":133,"sourceCode":"}\n\n/** Browser-based loopback PKCE login — the ADR-306 default for an interactive desktop. */\nexport async function browserLogin(print: (line: string) => void): Promise<LoginResult> {\n  const sec = await loadSecurityOAuth();\n  const server = await sec.CallbackServer.bind();\n  const pkce = sec.generatePkce();\n  const url = sec.authorizeUrl(server.redirectUri, pkce.state, pkce.codeChallenge);\n\n  print('Opening your browser to sign in to Cognitum...');\n  print(`If it doesn't open automatically, visit:\\n\\n  ${url}\\n`);\n  await sec.openBrowser(url).catch(() => {}); // best-effort — the URL above is always the fallback\n  print('Waiting for you to finish signing in...');\n\n  const result = await server.awaitCallback();\n  const validated = validateCallback(result.error, result.code, result.state, pkce.state);\n  if (!validated.ok) {\n    if (validated.reason === 'state-mismatch') throw new StateMismatchError();\n    throw new LoginDeniedError(validated.detail ?? 'unknown');\n  }\n\n  const tokens = await sec.exchangeCode(validated.code, pkce.codeVerifier, server.redirectUri);\n  return { tokens, method: 'pkce' };\n}\n\n/** Headless fallback: prints the authorize URL with the OOB redirect, prompts for the pasted code. */\nexport async function manualLogin(\n  print: (line: string) => void,\n  input: NodeJS.ReadableStream = process.stdin,\n): Promise<LoginResult> {\n  const sec = await loadSecurityOAuth();\n  print('Browser-based callback unavailable (SSH/container detected, or --no-browser).\\n');\n\n  const pkce = sec.generatePkce();\n  const url = sec.authorizeUrl(sec.OOB_REDIRECT_URI, pkce.state, pkce.codeChallenge);\n  print(`Open this URL in a browser and authorize:\\n\\n  ${url}\\n`);\n","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/auth/client.ts#L115-L151","documentation":"LoginDeniedError means the OAuth authorization server (or the callback) reported failure rather than success: validateCallback saw an explicit error parameter, or a callback with no authorization code at all. The detail string is the server's error value (e.g. access_denied, server_error) or 'no authorization code received'. This is the server/user saying 'no' — distinct from StateMismatchError, which is tampering/mismatch.","triggerScenarios":"browserLogin()/manualLogin flows where the user clicks 'Deny' on the consent screen (error=access_denied), the IdP returns server_error/temporarily_unavailable, or the callback fires with ?error=... or with neither code nor error (detail: 'no authorization code received').","commonSituations":"User cancels consent; the Cognitum auth surface is down or misconfigured (client_id/redirect_uri mismatch surfaces as invalid_request); corporate SSO policies auto-denying unknown apps; callback ports blocked so an empty request hits the loopback server.","solutions":["Read the detail: access_denied = user action (re-run and approve); server_error/temporarily_unavailable = retry later; invalid_request = client/redirect misconfiguration to report or fix","If it's consent denial, simply re-run `ruflo auth login` and approve","For persistent invalid_request, verify the CLI version matches the currently supported auth surface (the code targets auth.cognitum.one; stale clients may send unsupported params)","Fallback for constrained environments: `ruflo auth login --token-stdin` with an out-of-band token"],"exampleFix":"// before — any auth failure crashes the command\nawait browserLogin(print);\n\n// after — branch on the typed error class\ntry {\n  await browserLogin(print);\n} catch (e) {\n  if (e instanceof LoginDeniedError) {\n    print(`Login failed: ${e.message}`);\n    if (!e.message.includes('access_denied')) print('The auth server may be down — try again later.');\n    process.exitCode = 1;\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"import { LoginDeniedError } from './auth/client.js';\n\ntry { await browserLogin(print); }\ncatch (e) {\n  if (e instanceof LoginDeniedError) {\n    const detail = e.message.replace('authorization was denied or failed: ', '');\n    if (detail === 'access_denied') { /* user refused: prompt to re-run */ }\n    else { /* server error: schedule retry, surface detail */ }\n  }\n  throw e;\n}","preventionTips":["Surface the embedded detail string — it distinguishes user denial from server failure","Update the CLI when the auth surface changes so request params stay valid","For automation, prefer --token-stdin over the browser flow"],"tags":["oauth","auth","access-denied","login"],"backgroundTag":"oauth-access-denied","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}