ZitadelSDK 1.0.0
Previous versions prior to v1.1.0 have known bugs that make it unstable.
See the version list below for details.
dotnet add package ZitadelSDK --version 1.0.0
NuGet\Install-Package ZitadelSDK -Version 1.0.0
<PackageReference Include="ZitadelSDK" Version="1.0.0" />
<PackageVersion Include="ZitadelSDK" Version="1.0.0" />
<PackageReference Include="ZitadelSDK" />
paket add ZitadelSDK --version 1.0.0
#r "nuget: ZitadelSDK, 1.0.0"
#:package ZitadelSDK@1.0.0
#addin nuget:?package=ZitadelSDK&version=1.0.0
#tool nuget:?package=ZitadelSDK&version=1.0.0
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
- ⚙️ Configuration
- 💻 SDK Usage
- 🔐 Authentication for Web APIs
- 📚 Examples
- 🎯 Advanced Topics
- 🐛 Troubleshooting
- 🔒 Security Best Practices
- 📦 Package Requirements
🚀 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"
Option A: JWT Profile (Recommended)
{
"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
- Log in to your ZITADEL instance.
- Create or select a service account.
- Navigate to Keys → New Key.
- Download the generated JSON file.
- Extract
keyId,key, anduserIdand place them inappsettings.jsonor user secrets.
Personal Access Token (PAT)
- Log in to your ZITADEL instance.
- Navigate to your service user.
- Create a new Personal Access Token (PAT).
- Copy the token and store it securely.
💻 SDK Usage
The SDK provides two approaches for accessing ZITADEL gRPC clients, each with different trade-offs:
Approach 1: Direct Client Injection (Recommended)
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
- Never Commit Secrets: Use user secrets, environment variables, or a managed secret store like Azure Key Vault.
- Prefer JWT Profile for M2M: Use the JWT Profile method for service-to-service authentication in production over static Personal Access Tokens.
- Use HTTPS: Always set
RequireHttpsMetadata = trueon your authentication handlers in production. - Principle of Least Privilege: Grant only the permissions necessary for your service account.
- Enable Caching: For the introspection handler, enable caching to reduce latency and load on your ZITADEL instance.
- Rotate Keys: Regularly rotate service account keys and Personal Access Tokens.
| Product | Versions 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. |
-
net8.0
- Duende.AspNetCore.Authentication.OAuth2Introspection (>= 6.3.0)
- Google.Protobuf (>= 3.33.0)
- Grpc (>= 2.46.6)
- Grpc.AspNetCore (>= 2.71.0)
- Grpc.Net.Client (>= 2.71.0)
- Grpc.Net.Common (>= 2.71.0)
- Grpc.Tools (>= 2.72.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.14.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.