.NET SDK

Async C# client for InferaDB with ASP.NET Core integration.

Coming soon. The .NET SDK is under active development. The API surface shown here is based on the Rust SDK and may change before release.

Async, strongly-typed client for InferaDB’s authorization APIs. Targets .NET 8+ with ASP.NET Core integration.

Installation

dotnet add package InferaDB.Sdk

Authentication

Three authentication methods:

Method Use Case Security
Client Credentials (Ed25519 JWT) Service-to-service High
Bearer Token User sessions, OAuth Medium
API Key Testing, simple integrations Basic
using InferaDB;

var client = new InferaDBClient(new InferaDBOptions
{
    Url = "https://engine.inferadb.com",
    Credentials = new ClientCredentials
    {
        ClientId = "my-client",
        PrivateKey = Ed25519PrivateKey.FromPemFile("client.pem"),
        CertificateId = "cert-id",
    },
});
builder.Services.AddInferaDB(options =>
{
    options.Url = "https://engine.inferadb.com";
    options.Credentials = new ClientCredentials
    {
        ClientId = "my-client",
        PrivateKey = Ed25519PrivateKey.FromPemFile("client.pem"),
        CertificateId = "cert-id",
    };
});

API Key

var client = new InferaDBClient(new InferaDBOptions
{
    Url = "https://engine.inferadb.com",
    ApiKey = Environment.GetEnvironmentVariable("INFERADB_API_KEY"),
});

Permission Checks

var vault = client.Organization("my-org").Vault("production");

// Simple check
bool allowed = await vault.CheckAsync("user:alice", "can_edit", "document:readme");

With ABAC Context

bool allowed = await vault.CheckAsync("user:alice", "can_view", "document:readme",
    new CheckOptions
    {
        Context = new Dictionary<string, object> { ["ip_address"] = "10.0.0.1" },
    });

Require — Throws on Deny

// Throws AccessDeniedException if permission is denied
await vault.RequireAsync("user:alice", "can_edit", "document:readme");

With Consistency Token

bool allowed = await vault.CheckAsync("user:alice", "can_view", "document:readme",
    new CheckOptions { AtLeastAsFresh = revisionToken });

Batch Check

var results = await vault.CheckBatchAsync(
[
    new CheckRequest("user:alice", "can_edit", "document:readme"),
    new CheckRequest("user:bob", "can_view", "document:readme"),
]);
if (results.AllAllowed)
{
    // all checks passed
}

Relationships

Write

// Returns a revision token
var token = await vault.Relationships.WriteAsync(
    new Relationship("document:readme", "editor", "user:alice"));

Batch Write

await vault.Relationships.WriteBatchAsync(
[
    new Relationship("document:readme", "editor", "user:alice"),
    new Relationship("document:readme", "viewer", "user:bob"),
]);

List

// Returns IAsyncEnumerable<Relationship>
var rels = await vault.Relationships
    .List(resource: "document:readme")
    .ToListAsync();

Delete

await vault.Relationships.DeleteWhereAsync(
    resource: "document:readme",
    relation: "viewer",
    subject: "user:bob");

Lookups

Both return IAsyncEnumerable<T>:

// What resources can Alice view?
var resources = await vault.Resources
    .AccessibleBy("user:alice")
    .WithPermission("can_view")
    .ResourceType("document")
    .ToListAsync();

// Who can edit this document?
var subjects = await vault.Subjects
    .WithPermission("can_edit")
    .OnResource("document:readme")
    .ToListAsync();

Testing

MockClient (Fastest)

using InferaDB.Testing;

var client = MockClient.Builder()
    .OnCheck("user:alice", "can_edit", "document:readme").Allow()
    .OnCheck("user:bob", "can_edit", "document:readme").Deny()
    .OnCheckAnySubject("can_view", "document:readme").Allow()
    .DefaultDeny()
    .VerifyOnDispose(true) // asserts all expectations were invoked on dispose
    .Build();

InMemoryClient (Full Policy Evaluation)

using InferaDB.Testing;

var client = InMemoryClient.WithSchemaAndData(
    schema: """
        type document {
            relation viewer
            relation editor
            relation can_view = viewer | editor
        }
        """,
    data:
    [
        new Relationship("document:readme", "editor", "user:alice"),
        new Relationship("document:readme", "viewer", "user:bob"),
    ]);

TestVault (Real Instance)

using InferaDB.Testing;

await using var vault = await TestVault.CreateAsync(org, schemaIpl);
// vault auto-cleans up on dispose
// call vault.Preserve() to keep data for debugging

Error Handling

using InferaDB;

try
{
    await vault.RequireAsync("user:alice", "can_edit", "document:readme");
}
catch (AccessDeniedException)
{
    // permission denied
}
catch (InferaDBException ex)
{
    if (ex.IsRetriable)
    {
        // retry after ex.RetryAfter
    }
    logger.LogError("Authorization error: Kind={Kind}, RequestId={RequestId}",
        ex.Kind, ex.RequestId);
}

ErrorKind enum: Unauthorized, Forbidden, NotFound, RateLimited, SchemaViolation, Unavailable, Timeout, InvalidArgument.

Framework Integrations

ASP.NET Core Policy

using InferaDB.AspNetCore;

builder.Services.AddAuthorization(options =>
{
    options.AddInferaDBPolicy("CanEditDocument", policy =>
    {
        policy.RequirePermission("can_edit");
        policy.WithSubject(ctx => $"user:{ctx.User.FindFirst("sub")?.Value}");
        policy.WithResource(ctx =>
            $"document:{ctx.HttpContext?.GetRouteValue("id")}");
    });
});
[Authorize(Policy = "CanEditDocument")]
[HttpPut("{id}")]
public async Task<IActionResult> UpdateDocument(string id, DocumentDto dto)
{
    // only reached if authorized
    return Ok(await documentService.Update(id, dto));
}

Minimal API

app.MapGet("/documents/{id}", async (
    string id,
    IInferaDBClient client,
    ClaimsPrincipal user,
    IDocumentService documentService) =>
{
    var vault = client.Organization("my-org").Vault("production");
    var userId = user.FindFirst("sub")?.Value
        ?? throw new UnauthorizedAccessException();
    await vault.RequireAsync($"user:{userId}", "can_view", $"document:{id}");
    return Results.Ok(await documentService.FindById(id));
})
.RequireAuthorization();