ZitadelSDK 1.0.0

Suggested Alternatives

ZitadelSDK 1.1.0

Additional Details

Previous versions prior to v1.1.0 have known bugs that make it unstable.

There is a newer version of this package available.
See the version list below for details.
dotnet add package ZitadelSDK --version 1.0.0
                    
NuGet\Install-Package ZitadelSDK -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ZitadelSDK" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ZitadelSDK" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="ZitadelSDK" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ZitadelSDK --version 1.0.0
                    
#r "nuget: ZitadelSDK, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ZitadelSDK@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ZitadelSDK&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=ZitadelSDK&version=1.0.0
                    
Install as a Cake Tool

ZITADEL SDK for ASP.NET Core

A comprehensive ASP.NET Core SDK for integrating with ZITADEL, featuring centralized gRPC client management, flexible authentication methods for your APIs, and a clean, fluent builder API for configuration.

🌟 Features

  • Fluent Builder API: Clean, intuitive configuration for the SDK's gRPC clients using .WithJwtAuth() and .WithPatAuth().
  • Centralized gRPC Client Management: Automatic authentication and lifetime management for ZITADEL's gRPC clients.
  • Multiple Authentication Methods: Supports JWT Profile (recommended for production) and Personal Access Tokens (for development).
  • API Authentication Handlers: Includes handlers for OAuth2 Introspection (for JWT and opaque tokens) and standard JWT Bearer validation to secure your web APIs.
  • Strongly-Typed Client Accessors: Easy, dependency-injectable access to Admin, Auth, Management, Settings, System, and User services.
  • Automatic Token Management: Smart caching and auto-refresh for JWT Profile tokens used by the SDK.
  • Role Claim Transformation: Automatic parsing of ZITADEL's organization-based role structure into standard ASP.NET Core role claims.
  • Production-Ready: Thread-safe, tested, and optimized for performance.

📋 Table of Contents


🚀 Quick Start

1. Install Required Packages

dotnet add package Grpc.Net.Client
dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package IdentityModel.AspNetCore.OAuth2Introspection

2. Configure appsettings.json

Choose either the JWT Profile (recommended for production) or a Personal Access Token for the SDK to authenticate its gRPC calls to ZITADEL.

🔒 Security Note: Never commit secrets to source control. Use user secrets for development: dotnet user-secrets set "ServiceAdmin:JwtProfile:Key" "your-key"

{
  "ServiceAdmin": {
    "Authority": "https://your-instance.zitadel.cloud",
    "JwtProfile": {
      "KeyId": "your-key-id",
      "Key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
      "UserId": "your-service-account-user-id"
    }
  }
}
Option B: Personal Access Token (For Development)
{
  "ServiceAdmin": {
    "Authority": "https://your-instance.zitadel.cloud",
    "PersonalAccessToken": "your-pat-token"
  }
}

3. Register Services in Program.cs

Configure the ZITADEL SDK for gRPC clients and add authentication handlers to protect your API endpoints.

using ZitadelTest.Services;
using ZitadelTest.Extensions;

var builder = WebApplication.CreateBuilder(args);

// ========================================
// ZITADEL SDK Configuration
// ========================================
// Configure how the SDK authenticates when making gRPC calls TO ZITADEL.
// Choose ONE of the following methods:

// ────────────────────────────────────────
// Method 1: Manual inline configuration
// ────────────────────────────────────────
builder.Services.AddZitadelSdk(builder.Configuration)
    .WithJwtAuth(config =>
    {
        config.KeyId = "your-key-id";
        config.Key = "-----BEGIN RSA PRIVATE KEY-----...";
        config.UserId = "user-id";
        config.AppId = "app-id";
    });

// ────────────────────────────────────────
// Method 2: Bind from appsettings.json section
// ────────────────────────────────────────
builder.Services.AddZitadelSdk(builder.Configuration)
    .WithJwtAuth(config =>
    {
        builder.Configuration.GetSection("ServiceAdmin:JwtProfile").Bind(config);
    });

// ────────────────────────────────────────
// Method 3: Auto-load from appsettings.json (requires: using ZitadelTest.Extensions;)
// ────────────────────────────────────────
builder.Services.AddZitadelSdk(builder.Configuration)
    .WithJwtAuth(builder.Configuration);  // Reads ServiceAdmin:JwtProfile section

builder.Services.AddZitadelSdk(builder.Configuration)
    .WithPatAuth(builder.Configuration);  // Reads ServiceAdmin:PersonalAccessToken key

// ────────────────────────────────────────
// Method 4: Personal Access Token (simple string)
// ────────────────────────────────────────
builder.Services.AddZitadelSdk(builder.Configuration)
    .WithPatAuth(builder.Configuration["ServiceAdmin:PersonalAccessToken"]!);

// ────────────────────────────────────────
// Method 5: Resolve token from DI (e.g., from secret manager)
// ────────────────────────────────────────
builder.Services.AddZitadelSdk(builder.Configuration)
    .WithPatAuth(sp =>
    {
        // Example: resolve from a secret manager service
        // var secretManager = sp.GetRequiredService<ISecretManager>();
        // return secretManager.GetSecret("ZitadelPat");

        // Or simply read from configuration via service provider
        var config = sp.GetRequiredService<IConfiguration>();
        return config["ServiceAdmin:PersonalAccessToken"]!;
    });

// ========================================
// ASP.NET Authentication (Optional)
// ========================================
// If you want to protect YOUR API endpoints with [Authorize], configure ASP.NET authentication.
// This is SEPARATE from the SDK configuration above (which is for making gRPC calls TO ZITADEL).

// ────────────────────────────────────────
// Option A: OAuth2 Introspection (validates access tokens by calling ZITADEL)
// ────────────────────────────────────────
builder.Services.AddAuthentication()
    .AddZitadelIntrospection(options =>
    {
        options.Authority = builder.Configuration["ServiceAdmin:Authority"]!;
        options.ClientId = builder.Configuration["ServiceAdmin:JwtProfile:AppId"]!;
        options.ClientSecret = builder.Configuration["ServiceAdmin:JwtProfile:Key"]!;
        options.CacheDuration = TimeSpan.FromMinutes(5);
    });

// ────────────────────────────────────────
// Option B: JWT Bearer (validates JWT tokens locally)
// ────────────────────────────────────────
builder.Services.AddAuthentication()
    .AddZitadelJwtBearer(options =>
    {
        options.Authority = builder.Configuration["ServiceAdmin:Authority"]!;
        options.Audience = builder.Configuration["ServiceAdmin:JwtProfile:AppId"]!;
    });

builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

4. Use in Your Code

Inject IZitadelSdk into your controllers or services to make authenticated gRPC calls to the ZITADEL API.

public class UserController : ControllerBase
{
    private readonly IZitadelSdk _sdk;

    public UserController(IZitadelSdk sdk)
    {
        _sdk = sdk;
    }

    [HttpGet("users")]
    public async Task<IActionResult> GetUsers()
    {
        var userClient = _sdk.GetClient<Zitadel.User.V2.UserService.UserServiceClient>();
        var response = await userClient.ListUsersAsync(
            new Zitadel.User.V2.ListUsersRequest());
        return Ok(response.Result);
    }
}

⚙️ Configuration

The SDK uses a fluent builder pattern for explicit configuration.

Authentication Comparison

This table compares the authentication methods for the SDK's gRPC client.

Feature JWT Profile Personal Access Token
Recommended For Production, CI/CD Development, Testing
Security ✅ High (rotating tokens) ⚠️ Medium (long-lived)
Setup Higher Lower
Token Lifecycle Auto-refresh (12h expiry) Manual rotation
Token Caching ✅ Built-in ❌ Not needed
Standards-Based ✅ OAuth 2.0 JWT Bearer ❌ Proprietary

How to Get Credentials

JWT Profile
  1. Log in to your ZITADEL instance.
  2. Create or select a service account.
  3. Navigate to KeysNew Key.
  4. Download the generated JSON file.
  5. Extract keyId, key, and userId and place them in appsettings.json or user secrets.
Personal Access Token (PAT)
  1. Log in to your ZITADEL instance.
  2. Navigate to your service user.
  3. Create a new Personal Access Token (PAT).
  4. Copy the token and store it securely.

💻 SDK Usage

The SDK provides two approaches for accessing ZITADEL gRPC clients, each with different trade-offs:

Register specific clients in Program.cs and inject them directly into your services.

Advantages:

  • Cleaner DI: Explicit dependencies in constructor
  • Better testability: Easy to mock specific clients
  • Prevents socket exhaustion: Clients properly managed by DI container
  • Type safety: Compile-time checking of injected clients
// In Program.cs - Register the clients you need
builder.Services.AddZitadelSdk(builder.Configuration)
    .WithJwtAuth(builder.Configuration)
    .AddZitadelClient<UserService.UserServiceClient>()
    .AddZitadelClient<ManagementService.ManagementServiceClient>();

// Or register multiple clients at once
builder.Services.AddZitadelSdk(builder.Configuration)
    .WithJwtAuth(builder.Configuration)
    .AddZitadelClients(
        ServiceLifetime.Scoped,  // Default is Scoped
        typeof(UserService.UserServiceClient),
        typeof(ManagementService.ManagementServiceClient),
        typeof(SessionService.SessionServiceClient)
    );
// In your controller - Inject the client directly
public class UserController : ControllerBase
{
    private readonly UserService.UserServiceClient _userClient;

    public UserController(UserService.UserServiceClient userClient)
    {
        _userClient = userClient;
    }

    [HttpGet("users")]
    public async Task<IActionResult> GetUsers()
    {
        var response = await _userClient.ListUsersAsync(
            new ListUsersRequest());
        return Ok(response.Result);
    }
}

Approach 2: Using sdk.GetClient<T>() (Flexible)

Inject IZitadelSdk and get clients on-demand.

Advantages:

  • Flexibility: Access any client without pre-registration
  • Dynamic: Choose clients at runtime
  • Cached: Clients are reused from internal cache

⚠️ Note: While clients are cached internally, registering them in DI (Approach 1) is more idiomatic for ASP.NET Core.

public class UserController : ControllerBase
{
    private readonly IZitadelSdk _sdk;

    public UserController(IZitadelSdk sdk)
    {
        _sdk = sdk;
    }

    [HttpGet("users")]
    public async Task<IActionResult> GetUsers()
    {
        // Get client on-demand
        var userClient = _sdk.GetClient<UserService.UserServiceClient>();
        var response = await userClient.ListUsersAsync(
            new ListUsersRequest());
        return Ok(response.Result);
    }
}

Available Client Types

Common ZITADEL gRPC clients you can register or access:

using Zitadel.User.V2;
using Zitadel.Management.V1;
using Zitadel.Admin.V1;
using Zitadel.Auth.V1;
using Zitadel.Session.V2;
using Zitadel.Settings.V2beta;
using Zitadel.System.V1;

// User management
UserService.UserServiceClient

// Organization and project management
ManagementService.ManagementServiceClient

// Instance administration
AdminService.AdminServiceClient

// User authentication operations
AuthService.AuthServiceClient

// Session management
SessionService.SessionServiceClient

// Instance settings
SettingsService.SettingsServiceClient

// System-level operations
SystemService.SystemServiceClient

Error Handling

Wrap gRPC calls in a try-catch block to handle potential RpcException errors.

try
{
    var response = await _userClient.ListUsersAsync(new());
    return Ok(response);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
{
    _logger.LogError(ex, "ZITADEL is unreachable");
    return StatusCode(503, new { error = "Service unavailable" });
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unauthenticated)
{
    _logger.LogError(ex, "Authentication failed. Check SDK credentials.");
    return StatusCode(401, new { error = "Unauthorized" });
}
catch (RpcException ex)
{
    _logger.LogError(ex, "An unexpected ZITADEL API error occurred.");
    return StatusCode(502, new { error = ex.Status.Detail });
}

🔐 Authentication for Web APIs

These authentication handlers are for protecting your API endpoints, not for authenticating the SDK's gRPC client.

OAuth2 Introspection

Recommended for web APIs that need to validate both JWT and opaque access tokens. It calls the ZITADEL introspection endpoint to validate tokens.

Configuration
builder.Services.AddAuthentication()
    .AddZitadelIntrospection(options =>
    {
        options.Authority = "https://your-instance.zitadel.cloud";
        options.ClientId = "your-client-id@your-project";
        options.ClientSecret = "your-client-secret";
        options.EnableCaching = true; // Recommended for performance
        options.CacheDuration = TimeSpan.FromMinutes(10);
    });

JWT Bearer

Standard, fast, and stateless JWT validation for APIs that only need to accept JWTs. The token is validated locally without calling ZITADEL.

Configuration
builder.Services.AddAuthentication()
    .AddZitadelJwtBearer(options =>
    {
        options.Authority = "https://your-instance.zitadel.cloud";
        options.Audience = "your-project-id"; // Set the project ID
    });

📚 Examples

Example 1: List All Users

[HttpGet("users")]
public async Task<IActionResult> ListUsers([FromQuery] int limit = 10)
{
    var userClient = _sdk.GetClient<Zitadel.User.V2.UserService.UserServiceClient>();
    var request = new ListUsersRequest
    {
        Query = new ListQuery { Limit = (uint)Math.Min(limit, 100) }
    };
    var response = await userClient.ListUsersAsync(request);
    return Ok(new
    {
        users = response.Result,
        total = response.Details?.TotalResult ?? 0
    });
}

Example 2: Get Current User Info from a Protected Endpoint

This shows how to access user claims after they have been authenticated by the JWT Bearer or Introspection handler.

[Authorize]
[HttpGet("me")]
public IActionResult GetCurrentUser()
{
    var userId = User.FindFirst(ZitadelClaimTypes.UserId)?.Value;
    var userName = User.Identity?.Name;
    var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList();
    var orgId = User.FindFirst(ZitadelClaimTypes.OrganizationId)?.Value;

    return Ok(new { userId, userName, roles, organizationId = orgId });
}

Example 3: Organization-Specific Role Authorization

Check if a user has a specific role within a specific organization.

[Authorize]
[HttpGet("admin/dashboard")]
public IActionResult GetAdminDashboard()
{
    var orgId = "123456789012345678"; // The organization to check
    var requiredRole = ZitadelClaimTypes.OrganizationRole(orgId);

    // Check if the user has the "admin" role within the specified organization
    if (!User.HasClaim(requiredRole, "admin"))
    {
        return Forbid();
    }

    return Ok(new { message = "Access granted to admin dashboard." });
}

🎯 Advanced Topics

Custom Credential Provider

Implement IZitadelCredentialProvider to integrate custom authentication logic, such as fetching tokens from Azure Key Vault or another source.

public class CustomCredentialProvider : IZitadelCredentialProvider
{
    public CallCredentials CreateCallCredentials(string authority)
    {
        return CallCredentials.FromInterceptor(async (context, metadata) =>
        {
            var token = await GetTokenFromCustomSource();
            metadata.Add("Authorization", $"Bearer {token}");
        });
    }

    private async Task<string> GetTokenFromCustomSource()
    {
        // Your custom logic here
        return "custom-token-from-vault";
    }
}

// Register it in Program.cs
builder.Services.AddZitadelSdk(options =>
{
    options.Authority = "https://your-instance.zitadel.cloud";
    options.CredentialProvider = new CustomCredentialProvider();
});

Multiple Authentication Schemes

Configure and use multiple authentication schemes for different parts of your API.

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddZitadelJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.Authority = "https://instance1.zitadel.cloud";
        options.Audience = "project1";
    })
    .AddZitadelIntrospection("IntrospectionScheme", options =>
    {
        options.Authority = "https://instance2.zitadel.cloud";
        options.ClientId = "client-id";
        options.ClientSecret = "client-secret";
    });

// Use a specific scheme on an endpoint
[Authorize(AuthenticationSchemes = "IntrospectionScheme")]
[HttpGet("special-endpoint")]
public IActionResult SpecialEndpoint() => Ok();

🐛 Troubleshooting

Error Solution
"No credential provider configured" Ensure you have added either .WithJwtAuth() or .WithPatAuth() after AddZitadelSdk() in Program.cs.
"JWT Profile configuration is invalid" Verify that KeyId, Key, and UserId are all present and correctly formatted in your configuration for the JwtProfile section.
"Unauthenticated" with gRPC calls Check that your SDK's PAT or JWT Profile credentials are correct and that the service account has the necessary permissions (IAM Owner or Org Owner role) in ZITADEL.
"401 Unauthorized" on API endpoints This is an API authentication issue, not an SDK one. Check if the token sent by the client is valid, not expired, and that your AddZitadelJwtBearer or AddZitadelIntrospection options are correct.
"Zitadel authority must be provided" Make sure the Authority property is set in the ServiceAdmin section of your appsettings.json.

🔒 Security Best Practices

  1. Never Commit Secrets: Use user secrets, environment variables, or a managed secret store like Azure Key Vault.
  2. Prefer JWT Profile for M2M: Use the JWT Profile method for service-to-service authentication in production over static Personal Access Tokens.
  3. Use HTTPS: Always set RequireHttpsMetadata = true on your authentication handlers in production.
  4. Principle of Least Privilege: Grant only the permissions necessary for your service account.
  5. Enable Caching: For the introspection handler, enable caching to reduce latency and load on your ZITADEL instance.
  6. Rotate Keys: Regularly rotate service account keys and Personal Access Tokens.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 156 12/8/2025
1.2.0 183 10/31/2025
1.1.0 162 10/26/2025
1.0.3 205 10/25/2025 1.0.3 is deprecated.
1.0.2 210 10/25/2025 1.0.2 is deprecated.
1.0.1 215 10/25/2025 1.0.1 is deprecated.
1.0.0 176 10/25/2025 1.0.0 is deprecated.