Official Rust SDK for AuthForge - license validation, HWID binding, signed payload verification, and background heartbeats.
The crate is published on crates.io/crates/authforge as authforge.
From your project root:
cargo add authforgeOr declare a semver range in Cargo.toml (for example 1.0 picks up 1.0.x patches):
[dependencies]
authforge = "1.0"To track the GitHub repo instead of a crates.io release:
[dependencies]
authforge = { git = "https://github.com/AuthForgeCC/authforge-rust" }Optional: pin a branch or revision with branch = "main" or rev = "…".
Clone or submodule this repo and point at the crate directory:
[dependencies]
authforge = { path = "../authforge-rust" }Adjust the path to match your layout (for example vendor/authforge-rust).
use authforge::{AuthForgeClient, AuthForgeConfig, HeartbeatMode};
fn main() {
let client = AuthForgeClient::new(AuthForgeConfig {
app_id: "your-app-id".into(),
app_secret: "your-app-secret".into(),
public_key: "your-public-key".into(),
heartbeat_mode: HeartbeatMode::Server,
on_failure: Some(Box::new(|err| {
eprintln!("Auth failed: {}", err);
std::process::exit(1);
})),
..Default::default()
});
match client.login("XXXX-XXXX-XXXX-XXXX") {
Ok(result) => println!("Authenticated! Expires: {}", result.expires_in),
Err(e) => eprintln!("Login failed: {:?}", e),
}
}| Field | Type | Default | Description |
|---|---|---|---|
app_id |
String |
required | Application ID from the dashboard |
app_secret |
String |
required | Application secret from the dashboard |
public_key |
String |
required | App Ed25519 public key (base64) from dashboard |
heartbeat_mode |
HeartbeatMode |
Local |
Local or Server heartbeat strategy |
heartbeat_interval |
u64 |
900 |
Heartbeat interval in seconds (any value ≥ 1; default 15 min) |
api_base_url |
String |
https://auth.authforge.cc |
API base URL |
on_failure |
Option<Box<dyn Fn(&str)+Send+Sync>> |
None |
Callback invoked when auth fails |
request_timeout |
u64 |
15 |
Request timeout in seconds |
session_ttl_seconds |
Option<u64> |
None (server default: 86400) |
Requested session token lifetime. Server clamps to [3600, 604800]; preserved across heartbeat refreshes. |
hwid_override |
Option<String> |
None |
Optional custom hardware/subject identifier. When set to Some(non-empty), the SDK uses it instead of generated device fingerprint data. |
let client = AuthForgeClient::new(AuthForgeConfig {
app_id: "YOUR_APP_ID".into(),
app_secret: "YOUR_APP_SECRET".into(),
public_key: "YOUR_PUBLIC_KEY".into(),
heartbeat_mode: HeartbeatMode::Server,
hwid_override: Some(format!("tg:{telegram_user_id}")), // or format!("discord:{discord_user_id}")
..Default::default()
});- 1
login()orvalidate_license()call = 1 credit (one/auth/validatedebit each). - 10 heartbeats on the same license = 1 credit (billed every 10th successful heartbeat).
A desktop app running 6h/day at a 15-minute interval burns ~3–4 credits/day. A server app running 24/7 at a 1-minute interval burns ~145 credits/day — pick the interval based on how fast you need revocations to propagate (they always land on the next heartbeat).
login(&self, license_key: &str) -> Result<LoginResult, AuthForgeError>validate_license(&self, license_key: &str) -> Result<LoginResult, AuthForgeError>— same/auth/validate+ verification aslogin, without storing session or starting heartbeat;on_failureis not called for network errors on this pathself_ban(&self, license_key: Option<&str>, session_token: Option<&str>, revoke_license: bool, blacklist_hwid: bool, blacklist_ip: bool) -> Result<(), AuthForgeError>logout(&self)is_authenticated(&self) -> boolget_session_data(&self) -> Option<serde_json::Value>get_app_variables(&self) -> Option<std::collections::HashMap<String, serde_json::Value>>get_license_variables(&self) -> Option<std::collections::HashMap<String, serde_json::Value>>
HeartbeatMode::Server: sends/auth/heartbeatrequests on each interval and verifies signatures.HeartbeatMode::Local: performs local signature + expiry checks without heartbeat network calls, and fails withSessionExpiredwhen the session timestamp is reached.
Errors are returned as AuthForgeError, including:
InvalidAppInvalidKeyExpiredRevokedHwidMismatchNoCreditsBlockedRateLimitedReplayDetectedAppDisabledSessionExpiredRevokeRequiresSessionBadRequestSignatureMismatchNetworkError(String)Other(String)
Retry behavior is handled inside the internal HTTP request layer:
rate_limited: retry after 2s, then 5s (max 3 attempts total)- network transport failure: retry once after 2s
- retries regenerate a fresh nonce when request payload includes
nonce
Use self_ban(...) when anti-tamper checks trigger:
// Post-session (authenticated): revoke + HWID/IP blacklist.
client.self_ban(None, None, true, true, true)?;
// Pre-session: provide a license key; SDK forces revoke off client-side.
client.self_ban(Some("AF-XXXX-XXXX-XXXX"), None, true, true, true)?;
// Explicit flags:
client.self_ban(None, None, false, true, true)?;self_ban(...) chooses request mode automatically:
- Uses post-session mode when a session token is available (
session_tokenarg or current SDK session). - Falls back to pre-session mode with
license_key+ nonce + app secret. - In pre-session mode, revoke is always disabled client-side to avoid unsafe key revocations.
MIT