rathole-org/rathole · error

Service failed the authentication

Error message

Service {} failed the authentication

What it means

Fires in do_control_channel_handshake when the client's Auth payload does not equal protocol::digest(service_token || nonce). The server computes the expected session key from the service's token plus the nonce it sent, and the client's response mismatched, so the client does not possess the service token. The server writes Ack::AuthFailed and fails the handshake; this is the authentication step for the control channel, not a transport failure.

Solutions

  1. Ensure the client's service token matches the server's configured token for that service (it must not be None)
  2. Check for clock/nonce corruption or protocol version mismatch between client and server
  3. Use the debug! hex output to compare expected vs received digests when debugging
  4. Rotate tokens consistently on both sides after any config change
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at src/server.rs:330 when the library encounters an invalid state.

Common situations: See trigger scenarios.

Understand the failure class


AI-assisted analysis of rathole-org/rathole@a292f7ed54 (2026-09-07). Data as JSON: /api/errors/f7872ae8420bf501. Report an issue: GitHub.

Appendix: source

Thrown at src/server.rs:330

    // Calculate the checksum
    let mut concat = Vec::from(service_config.token.as_ref().unwrap().as_bytes());
    concat.append(&mut nonce);

    // Read auth
    let protocol::Auth(d) = read_auth(&mut conn).await?;

    // Validate
    let session_key = protocol::digest(&concat);
    if session_key != d {
        conn.write_all(&bincode::serialize(&Ack::AuthFailed).unwrap())
            .await?;
        debug!(
            "Expect {}, but got {}",
            hex::encode(session_key),
            hex::encode(d)
        );
        bail!("Service {} failed the authentication", service_name);
    } else {
        let mut h = control_channels.write().await;

        // If there's already a control channel for the service, then drop the old one.
        // Because a control channel doesn't report back when it's dead,
        // the handle in the map could be stall, dropping the old handle enables
        // the client to reconnect.
        if h.remove1(&service_digest).is_some() {
            warn!(
                "Dropping previous control channel for service {}",
                service_name
            );
        }

        // Send ack
        conn.write_all(&bincode::serialize(&Ack::Ok).unwrap())
            .await?;
        conn.flush().await?;

View on GitHub (pinned to a292f7ed54)