fatedier/frp · error

token in heartbeat doesn't match token from configuration

Error message

token in heartbeat doesn't match token from configuration

What it means

Identical static-token check to error 147 but applied to Ping messages, and only when 'HeartBeats' is included in the additional auth scopes (authentication.additionalAuthScopes). frps recomputes md5(token + ping.Timestamp) and compares with pingMsg.PrivilegeKey in constant time; mismatch rejects the heartbeat.

Source

Thrown at pkg/auth/token.go:77

	newWorkConnMsg.Timestamp = time.Now().Unix()
	newWorkConnMsg.PrivilegeKey = util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp)
	return nil
}

func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
	if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
		return fmt.Errorf("token in login doesn't match token from configuration")
	}
	return nil
}

func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
	if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
		return nil
	}

	if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
		return fmt.Errorf("token in heartbeat doesn't match token from configuration")
	}
	return nil
}

func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
	if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
		return nil
	}

	if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
		return fmt.Errorf("token in NewWorkConn doesn't match token from configuration")
	}
	return nil
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Keep authentication.token identical on both sides and include HeartBeats in additionalAuthScopes on both
  2. If writing a custom client, set Ping.PrivilegeKey = md5(token || Ping.Timestamp) whenever the scope is enabled
  3. Restart both frpc and frps after any token change so no live session mixes old and new keys

Example fix

# before — scopes enabled only on frps
# frps.toml
authentication.additionalAuthScopes = ["HeartBeats"]

# after — mirror on frpc
# frpc.toml
authentication.additionalAuthScopes = ["HeartBeats"]
Defensive patterns

Strategy: validation

Validate before calling

expected := md5hex(authToken + strconv.FormatInt(pingMsg.Timestamp, 10))
if expected != pingMsg.PrivilegeKey {
    return errors.New("heartbeat auth failed; check token and additionalAuthScopes parity")
}

Prevention

When it happens

Trigger: HeartBeats scope enabled on frps and the ping PrivilegeKey was computed with a different token than frps's authentication.token — typically the Login succeeded (tokens matched then) but a second frpc process, or an edited config, sends pings with another token; or a custom client forgot to authenticate pings when the scope is enabled.

Common situations: Enabling additionalAuthScopes=["HeartBeats"] on frps while a custom/SDK client only authenticates Login; token rotated on frpc but frps not restarted (or vice versa) mid-session.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/4bff633dcc3d31e1. Report an issue: GitHub.