> ## Documentation Index
> Fetch the complete documentation index at: https://docs.authforge.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# C# SDK

> Integrate AuthForge into your .NET application with the official NuGet package.

## Requirements

* .NET 6.0 or later
* `BouncyCastle.Cryptography` (pulled in transitively with the **AuthForge** package)

## Installation

Add the **AuthForge** package from [NuGet](https://www.nuget.org/packages/AuthForge/):

```bash theme={null}
dotnet add package AuthForge
```

<Note>
  Prefer a source-only layout? Copy `AuthForgeClient.cs` from [GitHub](https://github.com/AuthForgeCC/authforge-csharp) and reference `BouncyCastle.Cryptography` explicitly. The NuGet package is recommended for most apps.
</Note>

## Quick start

```csharp theme={null}
using AuthForge;

var client = new AuthForgeClient(
    appId: "YOUR_APP_ID",
    appSecret: "YOUR_APP_SECRET",
    publicKey: "YOUR_PUBLIC_KEY",
    heartbeatMode: "SERVER"
);

Console.Write("Enter license key: ");
var key = Console.ReadLine() ?? "";

if (client.Login(key))
{
    Console.WriteLine("Authenticated!");
    // Your app logic here; heartbeats run in the background
}
else
{
    Console.WriteLine("Invalid license key.");
    Environment.Exit(1);
}
```

## Constructor parameters

```csharp theme={null}
var client = new AuthForgeClient(
    appId: "...",                   // Required; from dashboard
    appSecret: "...",               // Required; from dashboard
    publicKey: "YOUR_PUBLIC_KEY",   // Required; Ed25519 public key from your AuthForge dashboard (base64)
    heartbeatMode: "SERVER",        // Required; "SERVER" or "LOCAL"
    heartbeatInterval: 900,         // Optional; seconds (default: 900, any value ≥ 1 is supported)
    apiBaseUrl: "https://auth.authforge.cc",  // Optional
    onFailure: null,                // Optional; Action<string, Exception?>
    requestTimeout: 15,             // Optional; HTTP timeout in seconds
    ttlSeconds: null,               // Optional; requested session TTL. null = server default (24h). Clamped to [3600, 604800].
    hwidOverride: null              // Optional; custom identity (for example "tg:123456789")
);
```

### `ttlSeconds`

Requested session token lifetime in seconds for `/auth/validate`. Pass `null` (or omit) to accept the server default of 24 hours. The server clamps to `[3600, 604800]` (1 hour to 7 days). The requested TTL is preserved across heartbeat refreshes so long-running apps in LOCAL mode can extend their offline window up to 7 days.

## Billing

* Each successful `Login()` or `ValidateLicense()` costs **1 credit** (one `/auth/validate` debit).
* Heartbeats cost **1 credit per 10 successful calls** (billed on every 10th heartbeat). Any `heartbeatInterval ≥ 1` is economically safe.
* Revocations take effect on the **next** heartbeat regardless of interval.

## Login

```csharp theme={null}
bool success = client.Login(licenseKey);
```

Returns `true` if authentication succeeded, `false` otherwise. On success, the SDK starts a background heartbeat thread automatically.

## Validate license (no heartbeat)

```csharp theme={null}
var result = client.ValidateLicense(licenseKey);
if (result.Valid) {
    Console.WriteLine(result.SessionToken);
} else {
    Console.WriteLine(result.ErrorCode);
}
```

Same `/auth/validate` request and verification as `Login`, without updating the client’s session fields or starting the heartbeat thread.

## Failure callback

If authentication or a heartbeat fails, the SDK calls your `OnFailure` callback. If no callback is set (or the callback throws), the SDK calls `Environment.Exit(1)`.

```csharp theme={null}
void HandleFailure(string reason, Exception? exception)
{
    if (reason == "login_failed")
    {
        Console.WriteLine("Login failed; check your license key.");
    }
    else if (reason == "heartbeat_failed")
    {
        Console.WriteLine("Heartbeat failed; saving state.");
        SaveApplicationState();
    }
}

var client = new AuthForgeClient(
    appId: "YOUR_APP_ID",
    appSecret: "YOUR_APP_SECRET",
    publicKey: "YOUR_PUBLIC_KEY",
    heartbeatMode: "SERVER",
    onFailure: HandleFailure
);
```

<Warning>
  If you don't set `onFailure`, the SDK terminates the process immediately on any failure. Always set a callback in production to handle graceful shutdown.
</Warning>

## Reading variables

After a successful login, app variables and license variables are available:

```csharp theme={null}
if (client.Login(licenseKey))
{
    // App-wide variables
    var appVars = client.AppVariables;
    if (appVars.TryGetValue("maintenanceMode", out var maintenance) && maintenance is true)
    {
        Console.WriteLine("Server is under maintenance.");
        Environment.Exit(0);
    }

    // Per-license variables
    var plan = client.LicenseVariables.GetValueOrDefault("plan", "basic");
    if (plan?.ToString() == "pro")
    {
        EnableProFeatures();
    }
}
```

## Heartbeat modes

```csharp theme={null}
// SERVER mode; pings the API every interval
var client = new AuthForgeClient(
    appId: "...",
    appSecret: "...",
    publicKey: "YOUR_PUBLIC_KEY",
    heartbeatMode: "SERVER",
    heartbeatInterval: 900
);

// LOCAL mode; verifies locally, re-validates when the session token expires (default 24h; up to 7d via ttlSeconds)
var client = new AuthForgeClient(
    appId: "...",
    appSecret: "...",
    publicKey: "YOUR_PUBLIC_KEY",
    heartbeatMode: "LOCAL",
    heartbeatInterval: 900
);
```

See [Heartbeat Modes](/concepts#heartbeat-modes) for a detailed comparison.

## Full example (WPF)

```csharp theme={null}
using System.Windows;
using AuthForge;

public partial class App : Application
{
    private AuthForgeClient? _authClient;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        _authClient = new AuthForgeClient(
            appId: "YOUR_APP_ID",
            appSecret: "YOUR_APP_SECRET",
            publicKey: "YOUR_PUBLIC_KEY",
            heartbeatMode: "SERVER",
            onFailure: OnAuthFailure
        );

        var dialog = new LicenseDialog();
        if (dialog.ShowDialog() != true)
        {
            Shutdown();
            return;
        }

        if (!_authClient.Login(dialog.LicenseKey))
        {
            MessageBox.Show("Invalid license key.", "Authentication Failed",
                MessageBoxButton.OK, MessageBoxImage.Error);
            Shutdown();
            return;
        }

        var mainWindow = new MainWindow();
        mainWindow.Show();
    }

    private void OnAuthFailure(string reason, Exception? ex)
    {
        Dispatcher.Invoke(() =>
        {
            // Save user's work before shutting down
            (MainWindow as MainWindow)?.SaveState();

            MessageBox.Show(
                "License verification failed. Your work has been saved.",
                "Authentication Error",
                MessageBoxButton.OK,
                MessageBoxImage.Warning
            );

            Shutdown();
        });
    }
}
```

## GitHub

Full source, changelog, and issues: [AuthForgeCC/authforge-csharp](https://github.com/AuthForgeCC/authforge-csharp)
