JWT Authentication Implementation in OpenDeepWiki: A Complete Technical Guide

OpenDeepWiki implements JWT authentication using ASP.NET Core's JwtBearer middleware combined with custom JwtService and AuthService classes that handle token generation, validation, and role-based authorization through claims-based security.

OpenDeepWiki is an open-source wiki platform built on .NET that leverages JWT (JSON Web Token) authentication to secure API endpoints and manage user sessions. This article examines the complete JWT authentication implementation in the AIDotNet/OpenDeepWiki repository, covering configuration, token generation, validation, and authorization policies.

JWT Authentication Architecture Overview

The authentication system in OpenDeepWiki follows a layered architecture that separates configuration, service logic, and middleware integration. At its core, the system relies on four primary components working in concert:

  • JwtOptions – Configuration class holding signing keys, issuer, audience, and expiration settings
  • JwtService – Core service responsible for token generation and manual validation
  • AuthService – Business logic layer handling login/registration workflows
  • ASP.NET Core JwtBearer Middleware – Pipeline component that automatically validates incoming request tokens

This architecture ensures that token generation is isolated from business logic, while the middleware handles the repetitive task of request validation.

Core Configuration and Services

JwtOptions Configuration

The JwtOptions class defines the configuration schema for JWT settings. In src/OpenDeepWiki/Services/Auth/JwtOptions.cs, the system expects configuration values from appsettings.json or environment variables.

Key configuration parameters include:

  • SecretKey – The symmetric key used for HMAC-SHA256 signing
  • Issuer – The token issuer identifier
  • Audience – The intended recipient of the token
  • ExpirationMinutes – Token lifetime duration

In src/OpenDeepWiki/Program.cs (lines 73-81), the application binds these options and performs validation to ensure the SecretKey is configured:

// Program.cs - Configuration binding
builder.Services.Configure<JwtOptions>(options =>
{
    builder.Configuration.GetSection("Jwt").Bind(options);
    if (string.IsNullOrEmpty(options.SecretKey))
    {
        throw new InvalidOperationException("JWT SecretKey is not configured.");
    }
});

Service Registration in Program.cs

The dependency injection container is configured in src/OpenDeepWiki/Program.cs (lines 94-119) to register the authentication services and middleware. This registration includes:

  1. JWT Bearer Authentication – Configuring TokenValidationParameters with the symmetric security key
  2. Authorization Services – Adding role-based policy support
  3. Custom Service Registration – Binding IJwtService to JwtService and IAuthService to AuthService
// Program.cs - JWT Bearer configuration (lines 94-108)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        var jwtOptions = builder.Configuration.GetSection("Jwt").Get<JwtOptions>();
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = jwtOptions.Issuer,
            ValidAudience = jwtOptions.Audience,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(jwtOptions.SecretKey))
        };
    });

Additionally, lines 110-113 define the AdminOnly authorization policy for role-based access control:

// Program.cs - Authorization policies (lines 110-113)
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => 
        policy.RequireClaim(ClaimTypes.Role, "Admin"));
});

Token Generation and Validation

Generating Tokens with JwtService

The JwtService class in src/OpenDeepWiki/Services/Auth/JwtService.cs handles the cryptographic operations for token creation. The GenerateToken method (lines 22-47) constructs a JwtSecurityToken containing standard claims and role information.

The implementation uses symmetric key signing with HMAC-SHA256:

// JwtService.cs - Token generation (lines 22-47)
public string GenerateToken(User user, IEnumerable<string> roles)
{
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id),
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.Email, user.Email),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
    };

    // Add role claims for authorization
    foreach (var role in roles)
    {
        claims.Add(new Claim(ClaimTypes.Role, role));
    }

    var key = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(_jwtOptions.SecretKey));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: _jwtOptions.Issuer,
        audience: _jwtOptions.Audience,
        claims: claims,
        expires: DateTime.Now.AddMinutes(_jwtOptions.ExpirationMinutes),
        signingCredentials: creds);

    return new JwtSecurityTokenHandler().WriteToken(token);
}

Key security features implemented:

  • Unique Token ID (JTI) – Prevents token replay attacks using Guid.NewGuid()
  • Role-based Claims – Stores user roles as ClaimTypes.Role for policy evaluation
  • Expiration – Configurable lifetime via ExpirationMinutes

Token Validation

The JwtService also provides manual token validation through the ValidateToken method (lines 49-73), which is used for internal authorization checks beyond the middleware's automatic validation:

// JwtService.cs - Token validation (lines 49-73)
public bool ValidateToken(string token, out string userId)
{
    userId = null;
    var tokenHandler = new JwtSecurityTokenHandler();
    
    try
    {
        var validationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(_jwtOptions.SecretKey)),
            ValidateIssuer = true,
            ValidIssuer = _jwtOptions.Issuer,
            ValidateAudience = true,
            ValidAudience = _jwtOptions.Audience,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };

        var principal = tokenHandler.ValidateToken(token, validationParameters, out _);
        userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        return !string.IsNullOrEmpty(userId);
    }
    catch
    {
        return false;
    }
}

This manual validation method extracts the userId from the NameIdentifier claim after cryptographic verification, providing a fallback for scenarios where the middleware's HttpContext.User is not available.

Authentication Flow and Authorization

Login and Registration Process

The AuthService class in src/OpenDeepWiki/Services/Auth/AuthService.cs orchestrates the authentication workflow. During login (lines 14-18) and registration (lines 53-55), the service validates credentials and delegates token generation to JwtService:

// AuthService.cs - Login flow (lines 14-18)
public async Task<LoginResponse> LoginAsync(LoginRequest request)
{
    // ... credential validation logic ...
    var user = await _userManager.FindByEmailAsync(request.Email);
    // ... password verification ...
    
    var roles = await _userManager.GetRolesAsync(user);
    var token = _jwtService.GenerateToken(user, roles);
    
    return new LoginResponse
    {
        AccessToken = token,
        ExpiresIn = _jwtOptions.ExpirationMinutes * 60,
        User = _mapper.Map<UserDto>(user)
    };
}

The LoginResponse model (defined in src/OpenDeepWiki/Models/Auth/LoginResponse.cs) packages the access_token, expiration time in seconds, and serialized user information for the client.

Role-Based Authorization Policies

OpenDeepWiki implements claims-based authorization using ASP.NET Core's policy system. As configured in Program.cs (lines 110-113), the AdminOnly policy restricts access to users possessing the "Admin" role claim:

// Program.cs - Authorization policy configuration (lines 110-113)
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => 
        policy.RequireClaim(ClaimTypes.Role, "Admin"));
});

Controllers apply these policies using the [Authorize] attribute:

  • [Authorize] – Requires valid JWT (any authenticated user)
  • [Authorize(Policy = "AdminOnly")] – Requires Admin role claim

The middleware automatically populates HttpContext.User with claims extracted from the validated JWT, enabling role checks via User.IsInRole("Admin") or policy-based authorization.

Practical Implementation Examples

Client-Side Token Usage

When consuming the OpenDeepWiki API, clients first authenticate to receive a JWT, then include it in subsequent requests:

// Authenticate and receive token
var loginDto = new {
    Email = "alice@example.com",
    Password = "SecretPwd123"
};

var response = await httpClient.PostAsJsonAsync("/api/auth/login", loginDto);
var result   = await response.Content.ReadFromJsonAsync<LoginResponse>();

string accessToken = result.AccessToken;
int expiresIn      = result.ExpiresIn;

// Attach token to subsequent requests
httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

// Access protected resource
var profile = await httpClient.GetFromJsonAsync<UserInfo>("/api/user/me");

Server-Side Resource Protection

Controllers protect endpoints using authorization attributes, accessing claims from the validated token:

[ApiController]
[Route("api/user")]
public class UserController : ControllerBase
{
    // Any authenticated user
    [HttpGet("me")]
    [Authorize]
    public async Task<ActionResult<UserInfo>> GetCurrentUser()
    {
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        var user   = await _authService.GetUserInfoAsync(userId);
        return Ok(user);
    }

    // Admin-only access
    [HttpDelete("{id}")]
    [Authorize(Policy = "AdminOnly")]
    public async Task<IActionResult> DeleteUser(string id)
    {
        await _authService.DeleteUserAsync(id);
        return NoContent();
    }
}

Manual Token Validation

For scenarios requiring token validation outside the middleware pipeline (such as background services or WebSocket connections), use the JwtService.ValidateToken method:

public async Task<string?> ExtractUserIdFromToken(string token)
{
    if (_jwtService.ValidateToken(token, out var userId))
    {
        return userId;
    }
    
    return null; // Token invalid or expired
}

Summary

OpenDeepWiki implements a production-ready JWT authentication system through the following key mechanisms:

  • Symmetric Key Signing – Uses HMAC-SHA256 with a configurable secret key stored in JwtOptions
  • Claims-Based Identity – Embeds user ID, username, email, and roles as standard claims (NameIdentifier, Name, Email, Role)
  • Dual Validation – Combines automatic middleware validation via JwtBearer with manual JwtService.ValidateToken for flexibility
  • Policy-Driven Authorization – Implements role-based access control through ASP.NET Core authorization policies like AdminOnly
  • Configurable Expiration – Supports adjustable token lifetimes via ExpirationMinutes setting

The implementation spans Program.cs for middleware configuration, JwtService.cs for cryptographic operations, and AuthService.cs for business logic, providing a clean separation of concerns suitable for enterprise applications.

Frequently Asked Questions

How does OpenDeepWiki store the JWT signing key?

The signing key is stored in the JwtOptions configuration class, which binds to the Jwt section in appsettings.json or environment variables. In src/OpenDeepWiki/Program.cs (lines 73-81), the application validates that SecretKey is not empty during startup to prevent runtime cryptographic failures.

What claims are included in the OpenDeepWiki JWT tokens?

OpenDeepWiki tokens include standard identity claims: NameIdentifier (user ID), Name (username), Email, and Jti (unique token ID for replay protection). Additionally, role claims using ClaimTypes.Role are embedded for each user role to support the AdminOnly authorization policy and other role-based access controls.

Can tokens be validated outside of the HTTP request pipeline?

Yes. While the JwtBearer middleware automatically validates tokens for incoming HTTP requests, the JwtService class in src/OpenDeepWiki/Services/Auth/JwtService.cs (lines 49-73) provides a ValidateToken method for manual validation. This is useful for WebSocket connections, background services, or scenarios where HttpContext is not available.

How does OpenDeepWiki handle role-based authorization?

OpenDeepWiki uses ASP.NET Core's policy-based authorization. In src/OpenDeepWiki/Program.cs (lines 110-113), an AdminOnly policy is defined that requires the ClaimTypes.Role claim with value "Admin". Controllers apply this using [Authorize(Policy = "AdminOnly")] attributes, while standard [Authorize] attributes require only valid authentication without specific role requirements.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →