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
- Verify configuration.serverUrl points at the correct Happy server (check HAPPY_SERVER_URL env var).
- Delete ~/.handy/access.key so the CLI regenerates a fresh key pair and re-authenticates.
- Inspect the raw /v1/auth response (curl with the same payload) to see the server's success=false reason.
- Upgrade the CLI to the latest version to ensure the auth payload matches the server's expected schema.
- 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
- Pin HAPPY_SERVER_URL explicitly in your shell profile or daemon config.
- Re-pair (delete ~/.handy/access.key) after switching servers.
- Keep the CLI updated so auth payload format matches the server.
- Test connectivity to /v1/auth with curl before reporting auth bugs.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Server unavailable
- Token exchange failed: ${tokenResponse.statusText}
- Token exchange failed: ${error}
- Token exchange failed: ${error}
- Happy session lookup authentication expired for legacy accou
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/436456366acb42d3.
Report an issue: GitHub.