slopus/happy · error

Authentication failed

Error message

Authentication failed

What it means

authGetToken performs challenge-response authentication against the server's /v1/auth endpoint using a TweetNaCl-signed challenge. The server is expected to respond with {success: true, token: '...'}. When the response reports success=false or omits a token, the CLI rejects the auth handshake with this generic 'Authentication failed' error.

Source

Thrown at packages/happy-cli/src/api/auth.ts:33

 * @param serverUrl - The URL of the server to authenticate with
 * @param secret - The secret key to use for authentication
 * @returns The authentication token
 */
export async function authGetToken(secret: Uint8Array): Promise<string> {
  const { challenge, publicKey, signature } = authChallenge(secret);
  
  const response = await axios.post(`${configuration.serverUrl}/v1/auth`, {
    challenge: encodeBase64(challenge),
    publicKey: encodeBase64(publicKey),
    signature: encodeBase64(signature)
  }, {
    headers: {
      'X-Happy-Client': `cli/${configuration.currentCliVersion}`
    }
  });

  if (!response.data.success || !response.data.token) {
    throw new Error('Authentication failed');
  }

  return response.data.token;
}

/**
 * Generate a URL for the mobile app to connect to the server
 * @param secret - The secret key to use for authentication
 * @returns The URL for the mobile app to connect to the server
 */
export function generateAppUrl(secret: Uint8Array): string {
  const secretBase64Url = encodeBase64Url(secret);
  return `handy://${secretBase64Url}`;
}

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Verify configuration.serverUrl points at the correct Happy server (check HAPPY_SERVER_URL env var).
  2. Delete ~/.handy/access.key so the CLI regenerates a fresh key pair and re-authenticates.
  3. Inspect the raw /v1/auth response (curl with the same payload) to see the server's success=false reason.
  4. Upgrade the CLI to the latest version to ensure the auth payload matches the server's expected schema.
  5. Check for proxy/VPN interference that could rewrite the response body.

Example fix

// before
if (!response.data.success || !response.data.token) {
  throw new Error('Authentication failed');
}
// after
if (!response.data.success || !response.data.token) {
  throw new Error(`Authentication failed (server=${configuration.serverUrl}, reason=${response.data.error ?? 'unknown'})`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!configuration.serverUrl || !/^https?:\/\//.test(configuration.serverUrl)) throw new Error('HAPPY_SERVER_URL is not set to a valid http(s) URL');
if (!secret || secret.length === 0) throw new Error('No secret key loaded — run happy auth first');

Type guard

function isAuthResponse(d: unknown): d is { success: true; token: string } {
  return typeof d === 'object' && d !== null && (d as any).success === true && typeof (d as any).token === 'string' && (d as any).token.length > 0;
}

Try / catch

try {
  const token = await authGetToken(secret);
} catch (err) {
  if ((err as Error).message === 'Authentication failed') {
    logger.error(`Auth rejected for ${configuration.serverUrl} — delete ~/.handy/access.key to re-pair, and verify HAPPY_SERVER_URL`);
    process.exitCode = 1;
  } else throw err;
}

Prevention

When it happens

Trigger: POST ${serverUrl}/v1/auth returns an HTTP 200 whose body has success=false or a missing/empty token field — e.g. the server does not recognize the public key, the signature/challenge pair is malformed, or a proxy/API layer intercepts with a non-auth JSON body.

Common situations: Pointing HAPPY_SERVER_URL at the wrong server (dev server vs hosted api.happy-servers.com) that returns a success-shaped but non-auth response; a stale or corrupted ~/.handy/access.key whose public key is not registered; an outdated CLI whose auth payload format the server rejects; corporate proxies returning HTML/JSON error pages with 200 status.

Understand the failure class

Related errors


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/436456366acb42d3. Report an issue: GitHub.