A production-ready real-time chat application with asynchronous AI-powered translation built using ASP.NET Core 9, SignalR, Azure Cosmos DB, Redis, and Azure AI Translator (Text Translation REST API). Features multi-language chat rooms, dual authentication (Entra ID + OTP), read receipts, presence tracking, and comprehensive observability.
Status: Production-ready | License: MIT | Tests: 193 passing β
SignalR Chat demonstrates modern real-time web application patterns with production-grade security, scalability, and observability. Built for learning and as a foundation for real-time features.
What it does:
- π Real-time messaging with SignalR (WebSocket + Server-Sent Events fallback)
- π Asynchronous AI-powered translation (Azure AI Translator, background workers; targets derived from room languages)
- π Dual authentication: Microsoft Entra ID (enterprise) + OTP fallback (Argon2id hashing, rate limiting)
- π₯ Fixed chat rooms: General, Tech, Random, Sports (no DMs)
- β Read receipts, typing indicators, presence tracking
- π 8 languages supported (i18n with server-side + client-side resources)
- π Full observability (OpenTelemetry, Azure App Insights, health checks)
- π Security headers (CSP with nonces, HSTS, frame protection)
What it doesn't do (non-goals):
- β No direct messages (DMs)
- β No message editing/deletion
- β No user registration (fixed users: alice, bob, charlie, dave, eve)
- β No file uploads or rich media
Run locally with in-memory storage (no external dependencies):
# Clone and build
git clone https://github.com/smereczynski/SignalR-Chat.git
cd SignalR-Chat
dotnet build ./src/Chat.sln
# Run with in-memory mode
Testing__InMemory=true dotnet run --project ./src/Chat.Web --urls=http://localhost:5099
# Open browser: http://localhost:5099
# Users: alice, bob, charlie, dave, eve
# OTP codes are printed to console (6-digit codes)What's running:
- In-memory OTP storage (no Redis)
- In-memory session state (no Cosmos DB)
- Direct SignalR connections (no Azure SignalR Service)
- All features work except persistence across restarts
β‘οΈ Next: Full local setup with Azure resources | Configuration guide
| Category | Description | Link |
|---|---|---|
| Getting Started | Installation, configuration, first run | docs/getting-started/ |
| Architecture | System design, diagrams, decisions | docs/architecture/ |
| Deployment | Azure deployment, Bicep, CI/CD | docs/deployment/ |
| Features | Authentication, presence, i18n, real-time | docs/features/ |
| Development | Local setup, testing, debugging | docs/development/ |
| Operations | Monitoring, diagnostics, health checks | docs/operations/ |
| Reference | API, configuration, telemetry | docs/reference/ |
Translation deep-dive: docs/architecture/translation-architecture.md
graph TD
Browser[Browser<br/>Razor Pages + SignalR.js]
subgraph "ASP.NET Core 9"
RazorPages[Razor Pages<br/>Login, UI]
SignalRHub[SignalR Hub<br/>ChatHub]
Auth[Cookie Authentication]
RazorPages --> Auth
SignalRHub --> Auth
end
Browser -->|HTTPS/WSS| RazorPages
Browser -->|WebSocket| SignalRHub
Auth --> CosmosDB[(Cosmos DB<br/>Messages, Rooms<br/>Read Status)]
Auth --> Redis[(Redis<br/>OTP Codes<br/>Rate Limits)]
SignalRHub --> CosmosDB
SignalRHub --> Redis
subgraph "Optional Azure Services"
AzureSignalR[Azure SignalR Service<br/>Scale to 1000s]
ACS[Azure Communication Services<br/>SMS/Email]
AppInsights[Application Insights<br/>Telemetry]
end
SignalRHub -.->|Optional| AzureSignalR
Auth -.->|Notifications| ACS
ASPNETCore -.->|Metrics/Logs| AppInsights
style Browser fill:#e1f5ff
style CosmosDB fill:#ffe1e1
style Redis fill:#ffe1e1
style Auth fill:#fff4e1
β‘οΈ Full architecture documentation
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | Vanilla JS, Bootstrap 5, SignalR Client | UI, real-time updates |
| Backend | ASP.NET Core 9, SignalR | Web server, WebSocket hub |
| Database | Azure Cosmos DB (NoSQL) | Messages, rooms, read receipts, translations |
| Cache | Redis | OTP storage, rate limiting, translation job queue |
| AI Translation | Azure AI Translator (Text Translation REST API) | Asynchronous message translation |
| Auth | Cookie authentication + Entra ID/OTP | Dual authentication: enterprise SSO + guest OTP |
| Observability | OpenTelemetry, App Insights | Metrics, traces, logs |
| Deployment | Azure App Service (Linux), Bicep IaC | Infrastructure as Code |
SignalR-Chat/
βββ src/
β βββ Chat.Web/ # Main ASP.NET Core application
β β βββ Controllers/ # REST API endpoints
β β βββ Hubs/ # SignalR ChatHub
β β βββ Pages/ # Razor Pages (login, chat UI)
β β βββ Services/ # OTP, presence, notifications
β β βββ Repositories/ # Cosmos DB data access
β β βββ Middleware/ # Security headers, logging
β βββ Chat.sln # Solution file
βββ tests/
β βββ Chat.Tests/ # Unit tests (included in Chat.sln)
β βββ Chat.IntegrationTests/# Integration tests (not included in Chat.sln)
β βββ Chat.Web.Tests/ # Web/security tests (not included in Chat.sln)
βββ infra/
β βββ bicep/ # Azure infrastructure (Bicep)
βββ docs/ # Documentation (detailed guides)
βββ .github/
β βββ workflows/ # CI/CD pipelines
βββ README.md # This file
193 unit tests (100% passing) in tests/Chat.Tests (this is the only test project included in src/Chat.sln):
# Run all tests
dotnet test src/Chat.sln
# Run specific test project
dotnet test tests/Chat.Tests/
# Optional: additional test projects exist under tests/, but are not referenced by Chat.sln
# dotnet test tests/Chat.IntegrationTests/
# dotnet test tests/Chat.Web.Tests/Note: additional test projects exist under tests/, but they are not currently referenced by src/Chat.sln.
β‘οΈ Testing guide
Deploy to Azure using Bicep infrastructure as code:
cd infra/bicep
# Deploy to dev environment
az deployment sub create \
--location westeurope \
--template-file main.bicep \
--parameters main.parameters.dev.bicepparam
# Deploy to production
az deployment sub create \
--location westeurope \
--template-file main.bicep \
--parameters main.parameters.prod.bicepparamAutomated CI/CD: GitHub Actions workflows deploy on push to main
β‘οΈ Deployment guide | Production checklist
- β Dual Authentication: Microsoft Entra ID (SSO) + OTP fallback (Argon2id hashing with pepper)
- β Multi-Tenant Support: AllowedTenants validation, UPN-based authorization
- β CORS Protection: Origin validation on SignalR hub, prevents CSRF attacks
- β CSP Headers: Nonce-based Content Security Policy, prevents XSS
- β HSTS: HTTP Strict Transport Security in production
- β Rate Limiting: Adaptive throttling for auth endpoints, 5 OTP attempts per 15 minutes
- β Input Sanitization: Log forgery prevention (CWE-117)
- β Private Endpoints: VNet integration, no public database access
The SignalR hub endpoint (/chatHub) enforces origin validation to prevent CSRF attacks. Configure allowed origins in appsettings.json:
Development (appsettings.Development.json):
{
"Cors": {
"AllowAllOrigins": true,
"AllowedOrigins": [
"http://localhost:5099",
"https://localhost:5099"
]
}
}Production (appsettings.Production.json):
{
"Cors": {
"AllowAllOrigins": false,
"AllowedOrigins": [
"https://signalrchat-prod-plc.azurewebsites.net"
]
}
}Azure App Service (set via environment variables):
Cors__AllowedOrigins__0=https://yourdomain.com
Cors__AllowAllOrigins=false
β οΈ Security:AllowAllOriginsMUST befalsein production. The application will throw an exception if set totruein non-Development environments.
β‘οΈ Security architecture | Authentication guide
Supports 8 languages with both server-side (.resx) and client-side (API) translations:
Languages: English, Polish, German, Czech, Slovak, Ukrainian, Lithuanian, Russian
# Add new language key
# 1. Edit Resources/SharedResources.resx (English default)
# 2. Add to Resources/SharedResources.[culture].resx
# 3. Rebuild to embed resources
# Verify translations
dotnet test tests/Chat.Tests/ --filter "Category=Localization"β‘οΈ Localization guide
Metrics, Traces, Logs via OpenTelemetry β Azure Application Insights (when configured)
Custom Metrics:
chat.otp.requests- OTP generation countchat.otp.verifications- OTP verification attemptschat.messages.sent- Messages sent per roomchat.connections.active- Active SignalR connections
Health Checks:
/healthz- Liveness probe (responds 200 OK)/healthz/ready- Readiness probe (checks Cosmos + Redis)/healthz/metrics- Lightweight in-process metrics snapshot
β‘οΈ Monitoring guide
We welcome contributions! Please read CONTRIBUTING.md for:
- Code of conduct
- How to set up your development environment
- Running tests and linters
- Commit message conventions
- Pull request process
Quick start for contributors:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make changes and add tests
- Run tests:
dotnet test src/Chat.sln - Commit:
git commit -m "feat: add my feature" - Push and create a pull request
This project is licensed under the MIT License - see LICENSE file for details.
- Documentation: docs/
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Changelog: CHANGELOG.md
- Original README: README.old.md (archived, detailed reference)
| Feature | Description | Implementation |
|---|---|---|
| Async Translation | Real-time AI translation of messages | Redis queue β Background workers β Azure AI Translator β SignalR broadcast |
| Translation Cache | Reduce API costs with 1-hour caching | Redis cache with translation results |
| Translation Retry | Automatic retry with exponential backoff | Max 3 attempts, high priority requeue |
| Optimistic Send | Messages appear instantly, confirmed async | SignalR invoke + server broadcast |
| Read Receipts | Per-user read status per message | Cosmos DB ReadReceipts container |
| Typing Indicators | "X is typingβ¦" shown to room | SignalR NotifyTyping with debounce |
| Presence Tracking | Online/offline status | Redis cache with TTL + SignalR events |
| Reconnection | Auto-reconnect with exponential backoff | SignalR.js with custom retry policy |
| Pagination | Load 50 messages, scroll to load more | Cosmos DB continuation token |
| Unread Badges | "5 unread in Tech" notifications | Redis counter + SignalR updates |
| Rate Limiting | Prevent OTP spam, abuse | ASP.NET Core rate limiter |
Made with β€οΈ using ASP.NET Core 9 & SignalR
