SWS is a C# application designed to host and execute REST and WebSocket handlers from plugin modules. It leverages ASP.NET Core for web hosting, providing a flexible and extensible platform for building real-time and traditional web applications. The application is designed to be configurable via command-line arguments, allowing users to specify the port and plugin directory.
SWS provides a flexible and extensible platform for building modular REST and WebSocket applications. By leveraging ASP.NET Core's DI container and a well-defined plugin architecture, developers can easily create custom handlers and extend the server's functionality.
SWS (Simple Web Server) is a lightweight, plugin-oriented, self-hosted web server framework designed for C#/.NET environments. It emphasizes extensibility, structured logging, role-based authentication, and modular handler-based routing for REST and WebSocket endpoints.
This section compares SWS with popular web server platforms like Node.js (with Express), Python's Flask/FastAPI, Java's Spring Boot, and Go's Gin/Fiber. It outlines when SWS may be a preferable choice, when it may not, and what features could be added to close the gap with more mature frameworks.
| Feature/Criteria | SWS (C#) | Node.js/Express | Flask/FastAPI (Python) | Spring Boot (Java) | Gin/Fiber (Go) |
|---|---|---|---|---|---|
| Language | C#/.NET | JavaScript/TypeScript | Python | Java | Go |
| Plugin-based handler loading | ✅ Yes | ❌ Manual (via require/import) | ❌ Manual via decorators | ✅ via Spring modules | ✅ via Go modules |
| Built-in structured logging | ✅ Yes | ❌ Needs middleware | ❌ Optional logging | ✅ via SLF4J | ❌ Needs manual impl |
| REST & WebSocket support | ✅ Yes | ✅ Yes | ✅ (w/ WebSocket lib) | ✅ Yes | ✅ Yes |
| Role-based static file middleware | ✅ Yes | ❌ Not built-in | ❌ Not built-in | ✅ Yes | ❌ Not built-in |
| JWT-based Auth Service | ✅ Yes | ✅ Yes (via middleware) | ✅ Yes (via FastAPI/JWT) | ✅ Yes | ✅ Yes |
| Dependency injection (DI) | ✅ Yes (via .NET Core) | ❌ Limited | ❌ Minimal (manual) | ✅ Yes | ❌ Limited |
| Middleware extensibility | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Hot reload / dev mode | ❌ Not yet | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Mature ecosystem | ❌ Early stage | ✅ Massive | ✅ Large | ✅ Enterprise-wide | ✅ Growing |
| Learning curve | ✅ Low | ✅ Low | ✅ Low | ❌ Moderate | ❌ Moderate |
| Performance (baseline) | ✅ High (Kestrel) | ✅ Moderate | ✅ Moderate | ❌ Moderate | ✅ High |
SWS is a great fit when:
- You want to write everything in C# and leverage .NET Core APIs.
- You value modularity and plugin-based extensibility.
- You need fine-grained role-based access to both dynamic and static content.
- You want to self-host without the weight of ASP.NET MVC or Razor.
- You are building developer tooling, internal systems, or embedded HTTP/WebSocket services.
You may want to consider another server framework when:
- You require a large ecosystem of battle-tested plugins (e.g., Passport.js for auth, middleware for CORS, compression, etc.).
- You want cross-platform developer availability (e.g., frontend and backend in JS).
- You need built-in templating/rendering engines.
- You need zero-config dev experience with auto-restart and live reload.
- You need native cloud support (e.g., Spring Boot for Kubernetes, or Express + Vercel).
To make SWS a more compelling general-purpose web server, the following enhancements may or may not be implemented in future revisions.
- Cookie signing + encryption (HttpOnly, Secure flags).
- CORS middleware with customizable policy.
- OAuth2/OpenID Connect support (Google, Microsoft).
- Rate-limiting & brute-force protection.
- Hot reload / live server dev mode.
- Built-in project scaffolding CLI.
- Templating support for SSR (e.g., Razor Lite, Scriban).
- File watching + auto-rebuild for plugins.
- REST metadata / Swagger support.
- gRPC or SignalR compatibility.
- OpenTelemetry tracing.
- Deployment recipes (Docker, Azure AppService).
- Versioned plugin contracts.
- Sandboxed plugin execution.
- Plugin discovery UI (e.g., web dashboard).
SWS is an effective, extensible web server designed for .NET developers who want a clean, modular base for building web APIs and real-time apps. Its plugin system, structured logging, and JWT-based auth are core strengths. To better compete head-to-head with platforms like Express or FastAPI, it would benefit from developer tooling, richer ecosystem integrations, and standards compliance. However, for internal tools, dashboards, and hybrid services where .NET is already in use, SWS provides a highly customizable and performant foundation.
This project utilizes several key technologies:
- ASP.NET Core: A cross-platform, open-source framework for building modern, cloud-based applications. It provides a robust foundation for handling HTTP requests, routing, middleware, and more.
- Dependency Injection (DI): A design pattern that allows components to be loosely coupled by providing dependencies from an external source. In ServerApp, DI is used to manage the lifecycle of logging providers and WebSocket handlers. ASP.NET Core's built-in DI container is used to register and resolve these dependencies.
- REST (Representational State Transfer): An architectural style for designing networked applications. RESTful APIs use standard HTTP methods (GET, POST, PUT, DELETE) to interact with resources.
- WebSockets: A communication protocol that provides full-duplex communication channels over a single TCP connection. This enables real-time, bidirectional communication between a client and a server.
ServerApp utilizes Mono.Options for parsing command-line arguments. The following arguments are supported:
--port <port>: Specifies the port to listen on (default: 5000).--modules <path>: Specifies the relative path to the server directory to look for REST handlers.--content <path>: Specifies the content folder relative to the server to host.--help: Displays help information.
- Create a new plugin project: Create a new class library project targeting .NET 9.0.
- Implement the
IWebSocketHandlerorIRestHandlerinterface: Create a class that implements the desired interface and registers your handler with the application. - Add a
.csprojfile: Add a reference to theServerAppproject in your plugin's.csprojfile. - Build and run: Build your plugin project and place the resulting DLL in the plugins directory specified by the
--modulescommand-line argument.
Program.cs: The entry point of the application. Handles command-line argument parsing and initializes the ASP.NET Core host.Startup.cs: Configures the application's services and middleware pipeline.IWebSocketHandlerInterface: Defines the contract for WebSocket handlers within plugins.SocketRoute: The URL path that triggers the WebSocket connection.Task HandleAsync(HttpContext context, WebSocket socket): Handles incoming and outgoing WebSocket messages.
IRestHandlerInterface: Defines the interface for REST handlers.Route: The URL path that triggers the Rest handler.Task<string> HandleAsync(HttpRequest request): Handles the incoming Rest request and provides a response to the client.
CustomLoggerProviderandCustomLogger: Implements a logging provider that integrates with the application's and plug-ins logging infrastructure. Plug-ins should generally use the ILogger pattern, and internal code should primarily use the Log class
ServerApp utilizes a custom logging provider to provide detailed information about application events. Current implementation writes all logged content to the console. Future versions may improve logging in various ways (e.g. optionally also writing to a file and/or telemetry, and/or some sort of web service).
- REST:
- Pros: Simple to implement, widely supported, stateless (easy to scale), well-suited for traditional web applications and APIs.
- Cons: Not ideal for real-time communication, requires frequent polling to receive updates, higher latency.
- WebSockets:
- Pros: Real-time, bidirectional communication, lower latency, efficient for applications requiring frequent updates.
- Cons: More complex to implement, requires a persistent connection, can be more challenging to scale.
The REST example demonstrates a simple "Hello World" API endpoint. The core components are:
index.html: This HTML file provides a basic user interface for interacting with the REST API. It includes an input field for entering a name and a button to send a "Greet" request. JavaScript withinindex.htmlhandles the user interaction, constructing a POST request to the server with the entered name.- Rest Handler Plugin (
Plugin.HelloWorld): This plugin contains the logic for handling the incoming REST request. It receives the name from the request body, constructs a greeting message, and returns it as a JSON response. The plugin registers itself with the server, making the/api/helloendpoint accessible.
Relationship within the Server Project:
The index.html file acts as the client, sending requests to the server. The server, upon receiving the request, dispatches it to the registered REST handler plugin. The plugin processes the request and returns a response, which is then displayed on the index.html page. This demonstrates the plugin-based architecture, where functionality is modular and extensible.
The Chat example showcases a real-time chat application using WebSockets.
chat.html: This HTML file provides a simple chat interface. It includes a display area (<pre>element) for showing messages, an input field for typing messages, and a "Send" button. JavaScript withinchat.htmlestablishes a WebSocket connection to/ws/chaton the server. It sends messages to the server via the WebSocket connection and displays incoming messages in the chat log.- WebSocket Handler Plugin (
Plugin.WebSockets): This plugin implements the WebSocket handler logic. It receives incoming messages from clients connected via WebSockets and handles the communication. The plugin registers itself with the server, making the/ws/chatendpoint accessible for WebSocket connections.
Relationship within the Server Project:
The chat.html file acts as the client, establishing a persistent WebSocket connection to the server. The server, upon receiving a WebSocket connection request, dispatches it to the registered WebSocket handler plugin. The plugin handles the bidirectional communication, receiving messages from and sending messages to connected clients. This demonstrates the use of WebSockets for real-time communication within the ServerApp project.
This section explains the authentication model used by SWS server, including how JWT-based login and role-based access control are enforced across REST, WebSocket, and static content. It also covers how to extend the solution to fit with modern security standards and practices.
The server supports simple JWT-based authentication with optional role enforcement. Tokens are issued during login and stored as secure cookies. Middleware inspects these tokens and either allows or redirects access based on user identity and roles.
- /api/login — Authenticates users, issues JWT + refresh tokens
- /api/refresh — Refreshes an expired JWT using a valid refresh token
- /api/logout — Invalidates a refresh token and clears the session
- JWT Cookie — Stored as
auth_token; used by middleware for access checks - Middleware — Enforces authentication and role requirements for file and API access
- User submits credentials to
/api/login - If valid, the server returns:
- a signed JWT token (
auth_tokencookie) - a refresh token (stored server-side, returned in response)
- a signed JWT token (
- Client sets cookie and navigates to return URL (e.g.,
chat.html)
- Middleware (e.g.
RoleBasedStaticMiddleware) extracts the token from:Authorization: Bearer ...header- or
auth_tokencookie
- It uses
JwtAuthService.ValidateToken(...)to decode/verify and extract:usernamerole
- Static content under
/secure/<role>/requires a valid token with that role - Static content under
/secure/(no subfolder) requires only authentication - Requests are redirected to
/logon.html?return=...if not valid
REST handlers must implement IRestHandler. To require authentication, simply add the RequiresAuthentication attribute. If restricting to a role is required, specify said role in the RequiresAuthentication attribute's constructor.
[Route("api/hello")]
[RequiresAuthentication] // only logged-in users can access
public class HelloHandler : IRestHandler
{
private readonly IAuthService _auth;
public HelloHandler(IAuthService auth) => _auth = auth;
public async Task HandleAsync(HttpRequest request)
{
var user = _auth.CurrentUser();
var response = new { Message = $"Hello, {user.Username}" };
await request.HttpContext.Response.WriteAsJsonAsync(response);
}
}You can also use user.Role to apply role-based behavior.
Adding the RequiresAuthentication attribute will enforce authentication as a precondition to your HandleAsync method being invoked. However, WebSocket handlers may implement additional authentication handling manually in the handler:
[RequiresAuthentication]
class WebSocketHandler : public IWebSocketHandler { ...
public async Task HandleAsync(HttpContext context, WebSocket socket)
{
var token = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
if (string.IsNullOrEmpty(token) && context.Request.Cookies.TryGetValue("auth_token", out var cookieToken))
token = cookieToken;
var (valid, username, role) = _auth.ValidateToken(token);
if (!valid) { context.Response.StatusCode = 401; return; }
...
}<script type="module">
import { logon } from './auth.js';
await logon(username, password); // sets cookie
location.href = getReturnUrl(); // redirect after login
</script>const token = document.cookie.match(/auth_token=([^;]+)/)?.[1];
fetch('/api/hello', { headers: { Authorization: `Bearer ${token}` } });
const ws = new WebSocket(`ws://${location.host}/ws/chat`);- Server issues a long-lived
refresh_tokenon login /api/refreshissues a new JWT if the refresh token is valid- Refresh tokens are stored in memory and cleared on
/api/logout
- Call
/api/logoutviaPOSTto invalidate session - Redirect to
logout.htmlorlogon.htmlafter - Server removes refresh token from in-memory store
To extend this to standards-compliant solutions:
- Replace
JwtAuthServicewith a wrapper around IdentityServer4 or Auth0 - Support auth code grant and PKCE flow
- Token validation will use
Microsoft.AspNetCore.Authentication.JwtBearer
- Add
HttpOnly,SameSite, andSecureflags to cookies - Use HTTPS (via
UseHttpsRedirection())
- Store refresh tokens in a database or distributed cache
- Optionally add expiration or session TTL
- Externalize user store to LDAP or OAuth claims
- Expand middleware to support hierarchical or scoped roles
- Built-in JWT-based authentication with cookie support
- Easy middleware-based access control by role
- Works with static files, REST, and WebSockets
- Extensible to OAuth2, SSO, and federated identity flows
For further customization or to integrate with corporate identity systems, replace the IAuthService implementation and optionally add distributed token revocation support.
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
If you have any questions or comments, you can send them our team directly!
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
If you are a security researcher and believe you have found a security vulnerability that meets the definition of a security vulnerability that is not resolved by the 10 Immutable Laws of Security, please send e-mail to us at secure@microsoft.com. To help us to better understand the nature and scope of the possible issue, please include as much of the below information as possible.
- Type of issue (buffer overflow, SQL injection, cross-site scripting, etc.)
- Product and version that contains the bug, or URL if for an online service
- Service packs, security updates, or other updates for the product you have installed
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue on a fresh install
- Proof-of-concept or exploit code
- Impact of the issue, including how an attacker could exploit the issue
Microsoft follows Coordinated Vulnerability Disclosure (CVD) and, to protect the ecosystem, we request that those reporting to us do the same. To encrypt your message to our PGP key, please download it from the Microsoft Security Response Center PGP Key. You should receive a response within 24 hours. If for some reason you do not, please follow up with us to ensure we received your original message. For further information, please visit the Microsoft Security Response Policy and Practices page and read the Acknowledgment Policy for Microsoft Security Bulletins.
For additional details, see Report a Computer Security Vulnerability on Technet