Tools
Tools: ASP.NET Core JWT Authentication: Setup, Validation, and Best Practices
2026-02-04
0 views
admin
Why does JWT authentication matter for modern APIs? ## What is a JWT and what problem does it solve? ## The problem it solves ## What JWT does not automatically solve ## What’s inside a JWT? ## What is in the header? ## What is in the payload? ## What is the signature? ## How does JWT authentication flow work end-to-end? ## How does ASP.NET Core authenticate requests with JWT bearer internally? ## How do you configure JWT bearer authentication in ASP.NET Core? ## How do you generate and sign JWT tokens securely? ## When should you prefer HMAC or RSA/ECDSA? ## How does ASP.NET Core validate JWTs at runtime? ## What checks run during validation? ## What happens when validation fails? ## How do you hook into validation events for diagnostics and hardening? ## How do claims-based authorization and policies work with JWTs? ## How should you handle token expiration, refresh, and logout? ## What is the practical strategy most teams use? ## What’s hard about revocation in stateless systems? ## How do you secure JWT authentication in production? ## What should you do to protect signing keys? ## Why is HTTPS non-negotiable? ## What is RequireHttpsMetadata and why does it matter? ## Where should clients store tokens safely? ## Which JWT attack vectors should you explicitly design against? ## What are the performance trade-offs of JWT authentication? ## How do you keep per-request validation cheap? ## When can JWT validation become a bottleneck? ## How do you monitor and troubleshoot JWT authentication failures? ## Task: Log failures without leaking security details ## What are the most common runtime causes of invalid tokens? ## Which common JWT mistakes cause security bugs? ## What does a production-ready JWT checklist look like? ## Summary: what should you do next? ## Related blogs JWT authentication in ASP.NET Core is a stateless, token-based approach where clients send a signed JWT (usually as a bearer token) and ASP.NET Core validates it on every request to build HttpContext.User. It’s for developers building APIs (REST, SPAs, mobile back ends, microservices) who want scalable authentication without server sessions. The key is understanding how tokens are structured, how they’re validated in the middleware pipeline, and how to handle lifetimes and refresh securely. (IETF Datatracker) Modern APIs are consumed by SPAs, mobile apps, partner services, and internal microservices, often across domains and deployments where cookie-based sessions are awkward or undesirable. JWTs are popular because the server can validate a request using only the token and its signing key (or issuer metadata) without loading per-user session state from a shared store. That “statelessness” improves horizontal scalability, but it also shifts responsibility to: strong validation rules, short token lifetimes, safe client storage, and robust monitoring. (IETF Datatracker) A JSON Web Token (JWT) is a compact, URL-safe token format for transferring claims between parties. JWTs are typically signed (JWS) so the receiver can verify integrity and authenticity; they can also be encrypted (JWE), though most “JWT auth” in APIs refers to signed tokens. JWTs solve the problem of proving identity (or client identity) to an API repeatedly without the API maintaining server-side login session state for every caller. JWTs do not automatically provide: JWT is a token format; standards like OAuth 2.0 and OpenID Connect are about flows and identity delegation. (ASP.NET Core supports JWT bearer validation either way—you just need correct validation settings.) (Microsoft Learn) A JWT has three Base64URL-encoded parts separated by dots: header.payload.signature. Typically, the header includes: Example (decoded JSON): The payload contains claims (registered + custom). Common registered claims include: The signature provides integrity/authenticity. It proves the token was issued by someone holding the signing key (HMAC) or private key (RSA/ECDSA). (IETF Datatracker) Critical misconception: JWTs are encrypted JWTs are encoded, not encrypted, unless you’re explicitly using JWE. Anyone who gets the token can Base64URL-decode the header and payload and read the claims. Do not put secrets or sensitive personal data in a JWT. Here’s the typical flow for APIs: Text diagram (request pipeline perspective): ASP.NET Core authentication is built around: Authentication determines who the caller is; authorization determines what they can access. When the JWT bearer handler succeeds, ASP.NET Core populates HttpContext.User with a ClaimsPrincipal. (Microsoft Learn) The canonical starting point is Microsoft’s JWT bearer configuration guidance. (Microsoft Learn) The outcome is a minimal Program.cs configuration that validates the issuer, audience, signature, or lifetime. The example uses a symmetric key for simplicity. In production, you may validate tokens from an external authority (OIDC) or use asymmetric keys for your own issuer. These switches matter because disabling core validation checks is a known insecure pattern (and even flagged by .NET analyzers). (Microsoft Learn) What should appsettings.json look like? Production note: Storing signing keys in appsettings is usually not appropriate—use a secrets manager or managed key store and rotate keys (details later). (If your org’s standard is different, this needs clarification from the security or infra team.) Which configuration mistakes cause most 401 Unauthorized issues? Token generation should happen only after you authenticate the user or client (password, SSO, client credentials, etc.). Your API should not mint tokens because someone asked. Task: Generate a short-lived access token with minimal claims The JwtSecurityTokenHandler API is widely used and documented, but note that IdentityModel guidance on NuGet indicates newer APIs exist (for some scenarios) and that some packages are considered legacy in newer IdentityModel versions. So keep an eye on library direction when upgrading. (Microsoft Learn) If you’re integrating with an external identity provider, you’ll typically validate using issuer metadata and rotating signing keys (JWKS) rather than hard-coding keys. ASP.NET Core’s JWT bearer handler supports this style of configuration. (Microsoft Learn) At runtime, validation is driven mainly by TokenValidationParameters. These checks are the difference between signed token parsing and secure authentication. (Microsoft Learn) The handler fails authentication, and the request will typically end up as: You can attach handlers like OnAuthenticationFailed or OnTokenValidated via JwtBearerOptions.Events. (Microsoft Learn) JWT authentication gives you a ClaimsPrincipal. Authorization uses that principal to enforce access rules. Outcome: Protect endpoints using policies (recommended over ad-hoc checks) Key design tip: Keep tokens small and policies expressive. If you keep adding claims to avoid a database call, you may end up with bloated tokens and brittle authorization logic. JWTs are stateless, so logout and revocation are not free. If you need refresh tokens, that’s typically an application-level feature (persistence, hashing, replay detection, revocation lists). ASP.NET Core can validate bearer tokens, but refresh-token design is your responsibility unless you adopt a full identity solution. Microsoft’s guidance also emphasizes selecting the right approach if you’re acting on behalf of a user and using OIDC. (Microsoft Learn) Once an access token is issued, it remains valid until it expires unless you: That’s why short-lived access tokens are the default recommendation for high-security systems. Bearer tokens are credentials. If intercepted, an attacker can replay them until expiration. If you validate tokens using an authority or metadata endpoint, HTTPS is required by default; disabling it should be limited to development. (Microsoft Learn) OWASP’s JWT guidance emphasizes preventing common token-handling mistakes (leakage, weak validation, etc.). (OWASP Cheat Sheet Series) JWT validation happens on every request to protected endpoints, so the main performance levers are: IdentityModel’s configuration system is designed to retrieve and refresh metadata, which helps reduce repeated network calls for keys and configuration. (Microsoft Learn) If you must do server-side checks, consider targeted enforcement (admin actions, money movement, data export) rather than all endpoints. Using handler events is the fastest way to see what’s going wrong during validation. (Microsoft Learn) Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
{ "alg": "HS256", "typ": "JWT" } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
{ "alg": "HS256", "typ": "JWT" } CODE_BLOCK:
{ "alg": "HS256", "typ": "JWT" } CODE_BLOCK:
{ "iss": "https://issuer.example", "aud": "api://orders", "sub": "user-123", "exp": 1736352000, "role": "Admin" } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
{ "iss": "https://issuer.example", "aud": "api://orders", "sub": "user-123", "exp": 1736352000, "role": "Admin" } CODE_BLOCK:
{ "iss": "https://issuer.example", "aud": "api://orders", "sub": "user-123", "exp": 1736352000, "role": "Admin" } COMMAND_BLOCK:
using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // If validating tokens from an OIDC authority, you’d often set: // options.Authority = "https://issuer.example"; // options.Audience = "api://orders"; var signingKey = builder.Configuration["Jwt:SigningKey"]; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidateAudience = true, ValidAudience = builder.Configuration["Jwt:Audience"], ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey!)), RequireExpirationTime = true, ValidateLifetime = true, // Keep this small; clock skew is a real-world reality, not a license for long tokens. ClockSkew = TimeSpan.FromMinutes(1), }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseHttpsRedirection(); // Order matters: app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // If validating tokens from an OIDC authority, you’d often set: // options.Authority = "https://issuer.example"; // options.Audience = "api://orders"; var signingKey = builder.Configuration["Jwt:SigningKey"]; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidateAudience = true, ValidAudience = builder.Configuration["Jwt:Audience"], ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey!)), RequireExpirationTime = true, ValidateLifetime = true, // Keep this small; clock skew is a real-world reality, not a license for long tokens. ClockSkew = TimeSpan.FromMinutes(1), }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseHttpsRedirection(); // Order matters: app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); COMMAND_BLOCK:
using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // If validating tokens from an OIDC authority, you’d often set: // options.Authority = "https://issuer.example"; // options.Audience = "api://orders"; var signingKey = builder.Configuration["Jwt:SigningKey"]; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidateAudience = true, ValidAudience = builder.Configuration["Jwt:Audience"], ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey!)), RequireExpirationTime = true, ValidateLifetime = true, // Keep this small; clock skew is a real-world reality, not a license for long tokens. ClockSkew = TimeSpan.FromMinutes(1), }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseHttpsRedirection(); // Order matters: app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); CODE_BLOCK:
{ "Jwt": { "Issuer": "https://issuer.example", "Audience": "api://orders", "SigningKey": "REPLACE_WITH_A_LONG_RANDOM_SECRET" } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
{ "Jwt": { "Issuer": "https://issuer.example", "Audience": "api://orders", "SigningKey": "REPLACE_WITH_A_LONG_RANDOM_SECRET" } } CODE_BLOCK:
{ "Jwt": { "Issuer": "https://issuer.example", "Audience": "api://orders", "SigningKey": "REPLACE_WITH_A_LONG_RANDOM_SECRET" } } CODE_BLOCK:
using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; public sealed class JwtTokenService { private readonly string _issuer; private readonly string _audience; private readonly SymmetricSecurityKey _key; public JwtTokenService(IConfiguration config) { _issuer = config["Jwt:Issuer"]!; _audience = config["Jwt:Audience"]!; _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:SigningKey"]!)); } public string CreateAccessToken(string userId, string role) { var claims = new List { new(JwtRegisteredClaimNames.Sub, userId), new(ClaimTypes.Role, role), }; var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _issuer, audience: _audience, claims: claims, notBefore: DateTime.UtcNow, expires: DateTime.UtcNow.AddMinutes(10), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; public sealed class JwtTokenService { private readonly string _issuer; private readonly string _audience; private readonly SymmetricSecurityKey _key; public JwtTokenService(IConfiguration config) { _issuer = config["Jwt:Issuer"]!; _audience = config["Jwt:Audience"]!; _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:SigningKey"]!)); } public string CreateAccessToken(string userId, string role) { var claims = new List { new(JwtRegisteredClaimNames.Sub, userId), new(ClaimTypes.Role, role), }; var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _issuer, audience: _audience, claims: claims, notBefore: DateTime.UtcNow, expires: DateTime.UtcNow.AddMinutes(10), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } } CODE_BLOCK:
using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; public sealed class JwtTokenService { private readonly string _issuer; private readonly string _audience; private readonly SymmetricSecurityKey _key; public JwtTokenService(IConfiguration config) { _issuer = config["Jwt:Issuer"]!; _audience = config["Jwt:Audience"]!; _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:SigningKey"]!)); } public string CreateAccessToken(string userId, string role) { var claims = new List { new(JwtRegisteredClaimNames.Sub, userId), new(ClaimTypes.Role, role), }; var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _issuer, audience: _audience, claims: claims, notBefore: DateTime.UtcNow, expires: DateTime.UtcNow.AddMinutes(10), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } } COMMAND_BLOCK:
.AddJwtBearer(options => { // ...TokenValidationParameters... options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents { OnAuthenticationFailed = context => { // Log context.Exception (avoid leaking details to clients) return Task.CompletedTask; }, OnTokenValidated = context => { // Optional: additional checks (e.g., user is disabled, token jti revoked) return Task.CompletedTask; } }; }); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
.AddJwtBearer(options => { // ...TokenValidationParameters... options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents { OnAuthenticationFailed = context => { // Log context.Exception (avoid leaking details to clients) return Task.CompletedTask; }, OnTokenValidated = context => { // Optional: additional checks (e.g., user is disabled, token jti revoked) return Task.CompletedTask; } }; }); COMMAND_BLOCK:
.AddJwtBearer(options => { // ...TokenValidationParameters... options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents { OnAuthenticationFailed = context => { // Log context.Exception (avoid leaking details to clients) return Task.CompletedTask; }, OnTokenValidated = context => { // Optional: additional checks (e.g., user is disabled, token jti revoked) return Task.CompletedTask; } }; }); COMMAND_BLOCK:
builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); options.AddPolicy("CanReadOrders", policy => policy.RequireClaim("scope", "orders.read")); }); Controller example: using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/orders")] public class OrdersController : ControllerBase { [HttpGet] [Authorize(Policy = "CanReadOrders")] public IActionResult GetOrders() => Ok(new { message = "orders" }); [HttpPost] [Authorize(Policy = "AdminOnly")] public IActionResult CreateOrder() => Ok(); } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); options.AddPolicy("CanReadOrders", policy => policy.RequireClaim("scope", "orders.read")); }); Controller example: using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/orders")] public class OrdersController : ControllerBase { [HttpGet] [Authorize(Policy = "CanReadOrders")] public IActionResult GetOrders() => Ok(new { message = "orders" }); [HttpPost] [Authorize(Policy = "AdminOnly")] public IActionResult CreateOrder() => Ok(); } COMMAND_BLOCK:
builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); options.AddPolicy("CanReadOrders", policy => policy.RequireClaim("scope", "orders.read")); }); Controller example: using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/orders")] public class OrdersController : ControllerBase { [HttpGet] [Authorize(Policy = "CanReadOrders")] public IActionResult GetOrders() => Ok(new { message = "orders" }); [HttpPost] [Authorize(Policy = "AdminOnly")] public IActionResult CreateOrder() => Ok(); } COMMAND_BLOCK:
var orders = app.MapGroup("/api/orders").RequireAuthorization("CanReadOrders"); orders.MapGet("/", () => Results.Ok(new { message = "orders" })); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
var orders = app.MapGroup("/api/orders").RequireAuthorization("CanReadOrders"); orders.MapGet("/", () => Results.Ok(new { message = "orders" })); COMMAND_BLOCK:
var orders = app.MapGroup("/api/orders").RequireAuthorization("CanReadOrders"); orders.MapGet("/", () => Results.Ok(new { message = "orders" })); - A login UI.
- User credential verification.
- Token refresh and revocation.
- Safe client-side storage. - alg (signing algorithm, e.g., HS256 / RS256)
- kid (key ID, common when keys rotate) - iss (issuer)
- aud (audience)
- exp (expiration)
- nbf (not before)
- iat (issued at)
- sub (subject) - Client authenticates (login/client credentials/federated sign-in).
- Issuer mints a JWT (adds claims, sets expiration, signs).
- Client calls API with Authorization: Bearer
- API validates token (signature, issuer, audience, lifetime).
- ASP.NET Core builds HttpContext.User from claims.
- Authorization checks run (policies/roles/claims) before controller/minimal endpoint executes (Microsoft Learn). - IAuthenticationService.
- Registered authentication handlers (e.g., JWT bearer handler).
- Authentication middleware that invokes those handlers. - UseAuthentication() missing or placed after UseAuthorization().
- Issuer or audience mismatch between token issuer and API validation.
- Wrong signing key or wrong algorithm.
- Token expired (clock skew misconfigured) (Microsoft Learn). - HMAC (HS256): Simplest; same secret signs and validates. Good when the same service issues and validates tokens (or you tightly control key distribution).
- RSA/ECDSA (RS256/ES256): Private key signs; public key validates. Better when multiple services validate tokens and you want safer distribution and key rotation via public keys. - Signature validation (ValidateIssuerSigningKey)
- Issuer check (ValidateIssuer)
- Audience check (ValidateAudience)
- Lifetime validation (ValidateLifetime, RequireExpirationTime)
- Clock skew tolerance (ClockSkew) (Microsoft Learn) - 401 Unauthorized (not authenticated).
- Or 403 Forbidden (authenticated but not authorized), depending on your endpoint and policies (Microsoft Learn). - Short-lived access tokens (minutes).
- Refresh tokens (longer-lived, stored server-side and revocable).
- Rotation (issue a new refresh token when used; revoke old one). - Check a server-side deny list (jti) during OnTokenValidated.
- Use very short expirations and rely on refresh controls. - Keep signing keys out of source control.
- Rotate keys deliberately (plan for kid and key rollover).
- Restrict who and what can mint tokens. - Browser apps: Avoid local storage for long-lived tokens if you can. Focus on preventing token leakage via XSS and adopting safer patterns.
- Native/mobile: Use secure OS keystores. - Token theft and replay (mitigate with short expiration, HTTPS, secure storage).
- Weak or disabled validation (issuer/audience/lifetime/signature must be on) (Microsoft Learn).
- Over-trusting claims (treat claims as inputs, not truth, unless you trust the issuer).
- Putting secrets in the payload (payload is readable) (IETF Datatracker). - Keep tokens small with fewer and lighter claims.
- Avoid custom validation that forces database calls on every request. Use it sparingly, e.g., only for high-risk endpoints.
- Prefer platform caching for issuer metadata and keys when using an authority. - Very high RPS APIs with large tokens.
- Heavy custom claim transformations.
- Per-request revocation checks that hit a central database. - Log authentication failures from OnAuthenticationFailed.
- Include correlation IDs and trace IDs.
- Avoid returning exception details to clients. - Issuer or audience mismatch.
- Signing key mismatch (wrong secret or rotated key without kid support).
- Token expired or server clock drift.
- Algorithm mismatch. - Disabling issuer, audience, or lifetime validation just to make it work. It often survives into production. (Microsoft Learn)
- Long-lived access tokens, which turn token theft into prolonged access.
- Storing sensitive data in the token payload. It’s readable. (IETF Datatracker)
- Confusing authentication with authorization (authN ≠ access control).
- Rolling your own crypto. Use proven libraries and middleware. - HTTPS enforced everywhere, including metadata endpoints when using an authority. (Microsoft Learn)
- Strong signing keys and a rotation plan.
- ValidateIssuer, ValidateAudience, ValidateLifetime, and RequireExpirationTime enabled. (Microsoft Learn)
- Short-lived access tokens with the refresh strategy documented.
- Claims minimized, so no secrets and no unnecessary data. (IETF Datatracker)
- Authorization policies defined (roles/claims/policies).
- Monitoring enabled, with auth failures logged and anomaly detection considered.
- Integration tests cover: expired token, wrong audience, wrong issuer, wrong signature. - Configure JWT bearer auth with strict validation (issuer/audience/signature/lifetime). (Microsoft Learn)
- Keep access tokens short-lived and design refresh and revocation intentionally if you need them. (Microsoft Learn)
- Use policy-based authorization and keep JWT claims minimal and insensitive. (IETF Datatracker)
- Add diagnostics via JWT bearer events so you can debug 401 and 403 issues quickly. (Microsoft Learn) - Integrating eSignature Solution into your ASP.NET Core app using BoldSign APIs
- Modern Background Jobs in ASP.NET Core: Comparing Hosted Services, Hangfire, and Quartz.NET
how-totutorialguidedev.toaiservernetworkswitchdatabase