Rkd.Scalar adds a production-ready API documentation platform. It combines documentation, authentication helpers and security protections into a single fluent configuration layer.
Rkd.Scalar integrates:
- Scalar UI
- OpenAPI generation
- JWT Bearer authentication
- API Key authentication
- Basic authentication
- API versioning integration
- Scalar UI protection
- Default JWT login endpoint with built-in rate limiting (development / testing convenience)
All features are enabled through a simple fluent builder API.
Rkd.Scalar focuses on three principles:
- Simplicity – drastically reduce OpenAPI setup code
- Security – protect documentation and test endpoints safely
- Extensibility – modular feature-based architecture
- Minimal configuration
- Built-in Basic Authentication support
- Built-in JWT Bearer Authentication support
- Built-in API Key Authentication support
- Optional default JWT login endpoint for development and testing
- Scalar UI protection via Basic Auth
- Built-in API versioning integration
- Feature-based modular architecture
- Teams building internal APIs
- SaaS platforms exposing partner APIs
- Developers who want production-ready documentation fast
- Teams tired of complex Swagger configuration
Install via .NET CLI:
dotnet add package Rkd.Scalar
Or via Package Manager:
Install-Package Rkd.Scalar
Most APIs can enable Scalar with only a few lines:
// JWT validation only (recommended baseline)
builder.Services
.AddRkdScalar(builder.Configuration)
.WithVersioning("v1")
.WithBearerAuth(jwtOptions);// JWT + default login endpoint (development / testing only)
builder.Services
.AddRkdScalar(builder.Configuration)
.WithVersioning("v1")
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions)
.WithDefaultJwtLogin<AuthCredential>("/auth/login", 5, TimeSpan.FromMinutes(1));Run the application and open:
/scalar/v1
You now have:
- OpenAPI documentation
- Scalar UI
- JWT authentication
- A login endpoint for local testing (when using
WithDefaultJwtLogin)
For production, prefer exposing your own login endpoint using the services registered by Rkd.Scalar. See JWT in Production.
200+ lines of configuration
OpenAPI
JWT
Auth schemes
Versioning
Security definitions
Minimal setup example (development):
using Rkd.Scalar.Extensions;
using Rkd.Scalar.Security.Jwt;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthorization();
var jwtOptions = new JwtOptions
{
Secret = "SUPER_SECRET_KEY_MINIMUM_32_CHARACTERS",
Issuer = "MyApi",
Audience = "MyApiClient",
Expiration = TimeSpan.FromHours(2),
ValidateNotBefore = true
};
builder.Services
.AddRkdScalar(builder.Configuration)
.WithVersioning("v1")
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions)
.WithDefaultJwtLogin<AuthCredential>("/auth/login", 5, TimeSpan.FromMinutes(1));
var app = builder.Build();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseRkdScalar(new RkdScalarConfiguration
{
Title = "My API"
});
app.Run();Note: hardcoding the JWT secret is shown here only for brevity. In real applications, load it from environment variables, user secrets or a secret manager — never commit secrets to source control.
Rkd.Scalar allows full customization of Scalar through ConfigureScalar.
Simple UI configuration:
app.UseRkdScalar(new RkdScalarConfiguration
{
Title = "My API",
Theme = ScalarTheme.BluePlanet
});Advanced Scalar customization:
app.UseRkdScalar(new RkdScalarConfiguration
{
Title = "My API",
ConfigureScalar = opt =>
{
opt.DarkMode = true;
opt.Theme = ScalarTheme.BluePlanet;
}
});This gives direct access to ScalarOptions while still keeping Rkd.Scalar's simplified configuration.
| Feature | Swashbuckle | Rkd.Scalar |
|---|---|---|
| OpenAPI generation | ✔ | ✔ |
| Scalar UI | ❌ | ✔ |
| JWT login endpoint | ❌ | ✔ (dev) |
| API Key auth | manual | built-in |
| UI protection | ❌ | ✔ |
| Versioning integration | manual | built-in |
Rkd.Scalar can also be configured using appsettings.json.
Example configuration:
{
"RkdScalar": {
"Title": "My API",
"OpenApiRoutePattern": "/openapi/{documentName}.json",
"Theme": "BluePlanet"
}
}Program.cs:
...
app.MapControllers();
var scalarOptions =
builder.Configuration
.GetSection("RkdScalar")
.Get<RkdScalarConfiguration>()!;
scalarOptions.ConfigureScalar = opt =>
{
opt.DarkMode = true;
};
app.UseRkdScalar(scalarOptions);
app.Run();This approach is useful for:
- environment‑based configuration
- DevOps pipelines
- centralized configuration
Instead of building JwtOptions manually, you can bind it directly from a
configuration section using the section-name overloads:
appsettings.json:
{
"JwtOptions": {
"Secret": "SUPER_SECRET_KEY_MINIMUM_32_CHARACTERS",
"Issuer": "MyApi",
"Audience": "MyApiClient",
"Expiration": 2,
"ValidateNotBefore": true
}
}Expiration is expressed in hours.
Program.cs:
// Validation-only mode, bound from the "JwtOptions" section (default name)
builder.Services
.AddRkdScalar(builder.Configuration)
.WithBearerAuth();
// Or with credential validation, using a custom section name
builder.Services
.AddRkdScalar(builder.Configuration)
.WithBearerAuth<AuthCredential, LoginValidator>("Auth:Jwt");If the section is missing or invalid (empty Secret, non-positive
Expiration), the application fails fast at startup with a descriptive error.
Rkd.Scalar supports two JWT modes:
Use this mode when your API only validates bearer tokens issued elsewhere (an identity provider, an auth microservice, another API).
var jwtOptions = new JwtOptions
{
Secret = "SUPER_SECRET_KEY_MINIMUM_32_CHARACTERS",
Issuer = "MyApi",
Audience = "MyApiClient",
Expiration = TimeSpan.FromHours(2),
ValidateNotBefore = true
};
builder.Services
.AddRkdScalar(builder.Configuration)
.WithBearerAuth(jwtOptions);This configures JWT validation and OpenAPI Bearer security without requiring a credential model or validator.
Use this mode when your API itself issues tokens. It registers:
ICredentialValidator<TCredential>(your implementation)IJwtTokenService(token generation)
builder.Services
.AddRkdScalar(builder.Configuration)
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions);From here you have two options for the login endpoint:
- Production: implement your own login endpoint (recommended — see below)
- Development / testing: use
WithDefaultJwtLoginfor a zero-code endpoint
Example credential model:
public class AuthCredential
{
public required string Username { get; set; }
public required string Password { get; set; }
}This is the recommended approach for production.
WithBearerAuth<TCredential, TValidator>() registers everything you need to
issue tokens from your own endpoint: your ICredentialValidator<TCredential>
and the IJwtTokenService. You keep full control over the route, versioning,
rate limiting policy, logging, auditing and response shape.
builder.Services
.AddRkdScalar(builder.Configuration)
.WithVersioning("v1")
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions);using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Rkd.Scalar.Security.Contracts;
using Rkd.Scalar.Security.Jwt;
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/auth")]
public sealed class AuthController : ControllerBase
{
private readonly ICredentialValidator<AuthCredential> _validator;
private readonly IJwtTokenService _jwtService;
public AuthController(
ICredentialValidator<AuthCredential> validator,
IJwtTokenService jwtService)
{
_validator = validator;
_jwtService = jwtService;
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(
[FromBody] AuthCredential credential,
CancellationToken cancellationToken)
{
var identity = await _validator.ValidateAsync(credential, cancellationToken);
if (identity is null)
return Unauthorized();
var token = _jwtService.GenerateToken(identity);
return Ok(new
{
access_token = token.Token,
expires_at = token.ExpiresAtUtc
});
}
}Both ICredentialValidator<AuthCredential> and IJwtTokenService are resolved
from dependency injection — no extra registration required.
In production, the validator should check credentials against a real user store using password hashing — never plaintext comparison:
using Rkd.Scalar.Security.Contracts;
using System.Security.Claims;
public sealed class LoginValidator : ICredentialValidator<AuthCredential>
{
private readonly IUserRepository _users;
private readonly IPasswordHasher _hasher;
public LoginValidator(IUserRepository users, IPasswordHasher hasher)
{
_users = users;
_hasher = hasher;
}
public async Task<ClaimsIdentity?> ValidateAsync(
AuthCredential request,
CancellationToken cancellationToken = default)
{
var user = await _users.FindByUsernameAsync(
request.Username, cancellationToken);
if (user is null || !_hasher.Verify(request.Password, user.PasswordHash))
return null;
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
new(ClaimTypes.Name, user.Username)
};
claims.AddRange(user.Roles.Select(r => new Claim(ClaimTypes.Role, r)));
return new ClaimsIdentity(claims, "Bearer");
}
}IUserRepository and IPasswordHasher represent your own persistence and
hashing infrastructure (e.g. Microsoft.AspNetCore.Identity.PasswordHasher<T>,
BCrypt or Argon2).
- Load the JWT secret from environment variables or a secret manager
- Hash and verify passwords — never store or compare plaintext
- Apply your own rate limiting policy to the login route
(
[EnableRateLimiting("your-policy")]) - Log failed authentication attempts for auditing
- Consider refresh tokens and token revocation if your scenario requires them
⚠️ This feature is intended for development, testing and prototyping — not for production. It exposes a generic login endpoint with a fixed response shape and a simple fixed-window rate limiter. For production, build your own endpoint as shown in JWT in Production.
Rkd.Scalar can automatically expose a login endpoint that issues JWT tokens, which is convenient for testing your API through the Scalar UI without writing any authentication endpoint.
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions)
.WithDefaultJwtLogin<AuthCredential>("/auth/login", 5, TimeSpan.FromMinutes(1))This creates:
POST /auth/login
The request body is automatically bound to the credential model (TCredential).
This means the JSON payload must match the credential type configured in
WithBearerAuth<TCredential, TValidator>().
Example request:
{
"username": "admin",
"password": "123"
}Example response:
{
"access_token": "JWT_TOKEN",
"expires_at": "2026-01-01T12:00:00Z"
}The default login endpoint automatically configures ASP.NET Rate Limiting using the values you provide:
.WithDefaultJwtLogin<AuthCredential>(
"/auth/login",
5,
TimeSpan.FromMinutes(1))This means:
- Maximum 5 login attempts
- Within 1 minute
If exceeded, the API returns:
HTTP 429 Too Many Requests
Don't forget to add app.UseRateLimiter(); before app.UseAuthentication();
in your Program.cs file.
WithDefaultJwtLogin() requires JWT authentication with credential validation.
You must configure this before calling it:
.WithBearerAuth<TCredential, TValidator>(jwtOptions)The validation-only overload cannot issue login tokens:
.WithBearerAuth(jwtOptions) // ❌ not compatible with WithDefaultJwtLoginThe credential type used in WithDefaultJwtLogin<TCredential>() must be the
same used in WithBearerAuth<TCredential, TValidator>().
Correct usage:
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions)
.WithDefaultJwtLogin<AuthCredential>("/auth/login", 5, TimeSpan.FromMinutes(1))Incorrect usage (will throw an exception during startup):
.WithBearerAuth<BasicAuthCredentials, UiCredentialValidator>(jwtOptions)
.WithDefaultJwtLogin<AuthCredential>("/auth/login", 5, TimeSpan.FromMinutes(1))The login endpoint depends on the credential model registered for JWT authentication, therefore both methods must use the same request model type.
Important
ICredentialValidator<T> is provided by the Rkd.Scalar NuGet package
(Rkd.Scalar.Security.Contracts).
When implementing validators, you should use the interface from the package, not create your own interface with the same name. This interface defines the contract used internally by Rkd.Scalar authentication features (Basic, JWT, and API Key).
builder.Services
.AddRkdScalar(builder.Configuration)
.WithBasicAuth<UiCredentialValidator>();Validator example (illustrative only — in real applications validate against a user store with hashed passwords):
public class UiCredentialValidator : ICredentialValidator<BasicAuthCredentials>
{
public Task<ClaimsIdentity?> ValidateAsync(
BasicAuthCredentials request,
CancellationToken cancellationToken = default)
{
if (request.Username == "admin" && request.Password == "123")
{
var identity = new ClaimsIdentity(
new[] { new Claim(ClaimTypes.Name, request.Username) },
"Basic");
return Task.FromResult<ClaimsIdentity?>(identity);
}
return Task.FromResult<ClaimsIdentity?>(null);
}
}Rkd.Scalar supports API Key authentication using a request header.
Enable API Key support:
builder.Services
.AddRkdScalar(builder.Configuration)
.WithApiKeyAuth<ApiKeyValidator>();Requests must include the header:
X-API-Key: YOUR_API_KEY
Validator example (illustrative only — in real applications validate keys against a secure store):
using Rkd.Scalar.Security.ApiKey;
using Rkd.Scalar.Security.Contracts;
using System.Security.Claims;
public class ApiKeyValidator : ICredentialValidator<ApiKeyCredentials>
{
public Task<ClaimsIdentity?> ValidateAsync(
ApiKeyCredentials request,
CancellationToken cancellationToken = default)
{
if (request.Key == "ABC123")
{
var identity = new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.Name, "ApiKeyUser"),
new Claim(ClaimTypes.Role, "SERVICE")
},
"ApiKey"
);
return Task.FromResult<ClaimsIdentity?>(identity);
}
return Task.FromResult<ClaimsIdentity?>(null);
}
}The API Key scheme will automatically appear in the Scalar authentication panel.
Example protected endpoint:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize(AuthenticationSchemes = "Bearer,Basic,ApiKey")]
[HttpGet("secure")]
public IActionResult SecureEndpoint()
{
return Ok("Authorized access");
}To require authentication before accessing the documentation UI:
builder.Services
.AddRkdScalar(builder.Configuration)
.WithUiProtection<UiCredentialValidator>();This protects:
- Scalar UI
- OpenAPI documents
using Basic Authentication.
When using WithUiProtection<TValidator>(), the recommended approach is to
implement the validator by injecting IConfiguration and reading credentials
directly from appsettings.json.
This keeps Program.cs clean and allows credentials to be managed per environment without changing code.
appsettings.json:
{
"UiCredentials": {
"Username": "myuser",
"Password": "mypassword"
}
}Validator implementation:
using System.Security.Claims;
using Microsoft.Extensions.Configuration;
using Rkd.Scalar.Security.Basic;
using Rkd.Scalar.Security.Contracts;
public sealed class UiCredentialValidator : ICredentialValidator<BasicAuthCredentials>
{
private readonly IReadOnlyDictionary<string, string> _credentials;
public UiCredentialValidator(IConfiguration configuration)
{
var section = configuration.GetSection("UiCredentials");
var creds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
// Format 1: UiCredentials:Username / UiCredentials:Password
var singleUsername = section["Username"];
var singlePassword = section["Password"];
if (!string.IsNullOrWhiteSpace(singleUsername) && singlePassword is not null)
creds[singleUsername] = singlePassword;
// Format 2: UiCredentials:{ "user1": "pass1", "user2": "pass2" }
foreach (var child in section.GetChildren())
{
if (child.Key.Equals("Username", StringComparison.OrdinalIgnoreCase) ||
child.Key.Equals("Password", StringComparison.OrdinalIgnoreCase))
continue;
if (!string.IsNullOrWhiteSpace(child.Key) && child.Value is not null)
creds[child.Key] = child.Value;
}
_credentials = creds;
}
public Task<ClaimsIdentity?> ValidateAsync(
BasicAuthCredentials request,
CancellationToken cancellationToken = default)
{
if (request is null ||
string.IsNullOrWhiteSpace(request.Username) ||
request.Password is null)
return Task.FromResult<ClaimsIdentity?>(null);
var valid = _credentials.TryGetValue(request.Username, out var expectedPassword) &&
string.Equals(expectedPassword, request.Password, StringComparison.Ordinal);
if (!valid)
return Task.FromResult<ClaimsIdentity?>(null);
var identity = new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.Name, request.Username),
new Claim(ClaimTypes.Role, "user")
},
authenticationType: "Basic");
return Task.FromResult<ClaimsIdentity?>(identity);
}
}This validator supports two credential formats in appsettings.json:
Single user:
{
"UiCredentials": {
"Username": "myuser",
"Password": "mypassword"
}
}Multiple users:
{
"UiCredentials": {
"alice": "password1",
"bob": "password2"
}
}Behavior:
- Username comparison is case-insensitive
- Password comparison is case-sensitive (exact match)
- Any username with a null password is rejected
Registration:
builder.Services
.AddRkdScalar(builder.Configuration)
.WithUiProtection<UiCredentialValidator>()
.WithBearerAuth(jwtOptions)
.WithLowercaseRouting();IConfiguration is injected automatically by ASP.NET's dependency injection
container. No additional registration is required.
Rkd.Scalar integrates with Asp.Versioning automatically.
Configuration:
builder.Services
.AddRkdScalar(builder.Configuration)
.WithVersioning("v1", "v2", "v3");Example controller:
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/payments")]
public class PaymentController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("API v1 running");
}
}Scalar automatically generates a document for each version.
To open Scalar automatically when the application starts:
Edit:
Properties/launchSettings.json
Enable browser launch:
"launchBrowser": trueSet Scalar as startup page:
"launchBrowser": true,
"launchUrl": "scalar/v1"Rkd.Scalar is built using a modular feature system.
Rkd.Scalar
Builder
Configuration
Extensions
Features
Middleware
OpenApi
Security
Each capability (JWT, Basic Auth, API Key Auth, Versioning, UI protection) is implemented as an independent feature.
This makes the library:
- easy to extend
- easy to maintain
- easy to evolve
- Extremely simple configuration
- Minimal OpenAPI boilerplate
- Built-in security features
- Modern Scalar documentation UI
- Versioning support
- Highly extensible architecture
- .NET 10
- ASP.NET Core Minimal APIs
- ASP.NET Core Controllers
A realistic production setup issues tokens through your own login endpoint
(see JWT in Production) and does
not use WithDefaultJwtLogin:
builder.Services
.AddRkdScalar(builder.Configuration)
.WithVersioning("v1", "v2", "v3")
.WithUiProtection<UiCredentialValidator>()
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions)
.WithApiKeyAuth<ApiKeyValidator>()
.WithLowercaseRouting();If your API does not issue tokens at all (they come from an external identity provider), prefer the simpler validation-only setup:
.WithBearerAuth(jwtOptions)For local development and prototyping, the default login endpoint removes all authentication boilerplate:
builder.Services
.AddRkdScalar(builder.Configuration)
.WithVersioning("v1")
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions)
.WithDefaultJwtLogin<AuthCredential>("/auth/login", 5, TimeSpan.FromMinutes(1));Below is a brief explanation of each feature available in Rkd.Scalar and the problem it is designed to solve.
.WithVersioning("v1", "v2", "v3")Enables API versioning and automatically exposes each version in the Scalar documentation UI.
Each version becomes selectable in the documentation interface, allowing developers to test and explore different API versions independently.
This feature integrates with ASP.NET API Versioning and configures the OpenAPI documents required for Scalar.
Typical use cases:
- Maintaining backward compatibility between API versions
- Gradually migrating clients between versions
- Supporting multiple client applications using different API versions
.WithUiProtection<UiCredentialValidator>()Protects the Scalar documentation interface using Basic Authentication.
This prevents unauthorized users from accessing the API documentation while still allowing the API itself to remain public if desired.
The provided validator (ICredentialValidator<BasicAuthCredentials>) is
responsible for validating the credentials used to access the UI.
Typical use cases:
- Restricting documentation access in production environments
- Allowing only internal teams to view API documentation
- Preventing accidental exposure of internal APIs
.WithBasicAuth<UiCredentialValidator>()Enables HTTP Basic Authentication support for API endpoints.
This feature registers the required OpenAPI security scheme and integrates the authentication flow so credentials can be provided directly from the Scalar UI when testing endpoints.
The validator implementation is responsible for validating the provided username and password.
Typical use cases:
- Internal APIs
- Simple service-to-service authentication
- Legacy integrations
Rkd.Scalar supports two JWT setup modes:
Validation-only (no token issuing):
.WithBearerAuth(jwtOptions)JWT with credential validation (token issuing support):
.WithBearerAuth<AuthCredential, LoginValidator>(jwtOptions)The validation-only mode configures token validation and the OpenAPI Bearer
scheme. The generic mode additionally registers
ICredentialValidator<TCredential> and IJwtTokenService, enabling token
issuing either from your own endpoint (production) or from
WithDefaultJwtLogin<TCredential>() (development).
This feature configures:
- JWT token validation
- OpenAPI security definitions
- Authentication middleware integration
Typical use cases:
- Modern API authentication
- Stateless authentication
- Mobile and SPA clients
.WithDefaultJwtLogin<AuthCredential>("/auth/login", 5, TimeSpan.FromMinutes(1))Registers a default login endpoint that issues JWT access tokens, intended for development, testing and prototyping.
The endpoint automatically binds the request body to the credential model
(TCredential) and validates it using the configured
ICredentialValidator<TCredential>.
Example endpoint:
POST /auth/login
The endpoint includes built-in rate limiting to protect against brute-force attempts during testing.
Example configuration above means:
- Maximum 5 login attempts
- Within 1 minute
If the limit is exceeded, the API returns:
HTTP 429 Too Many Requests
Typical use cases:
- Development environments
- Rapid prototyping
- Integration test scenarios
For production, implement your own authentication endpoint using the services
registered by WithBearerAuth<TCredential, TValidator>() — see
JWT in Production.
.WithApiKeyAuth<ApiKeyValidator>()Enables API Key authentication support.
Clients authenticate by sending an API key in the request header.
X-API-Key: YOUR_API_KEY
The provided validator (ICredentialValidator<ApiKeyCredentials>) is
responsible for validating the API key.
The feature automatically registers the OpenAPI security scheme so the API key can be provided directly from the Scalar UI.
Typical use cases:
- Partner integrations
- Machine-to-machine communication
- Public APIs with controlled access
.WithLowercaseRouting()Configures ASP.NET routing to generate lowercase URLs and query strings.
This improves URL consistency and avoids issues caused by case-sensitive routing in certain environments.
Benefits include:
- Consistent API URLs
- Better compatibility with proxies and gateways
- Improved SEO for public APIs
Planned features:
- OAuth2 support
- Extended Scalar customization
Pull requests are welcome.
Open an issue to propose new features or improvements.
MIT License
Built on the belief that API documentation should take minutes, not hours.