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
- Ensure the client's service token matches the server's configured token for that service (it must not be None)
- Check for clock/nonce corruption or protocol version mismatch between client and server
- Use the debug! hex output to compare expected vs received digests when debugging
- 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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
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)