Enhance your task management app by adding user authentication, task prioritization, file uploads, and real-time notifications with .NET Core, Angular, and SQL Server.

Introduction
In this blog, we will extend the Task Management Application we previously built, adding advanced features to make it more powerful and user-friendly. We will implement:
- User Authentication (with JWT tokens)
- Task Prioritization (with priority levels)
- File Uploads (for attaching files to tasks)
- Real-time Notifications (using SignalR for live updates)
These features will help you take your basic task management app to the next level by incorporating essential modern web application components.
User Authentication with JWT Tokens
To enable secure user authentication, we will implement JSON Web Tokens (JWT). JWT is a compact, URL-safe means of representing claims to be transferred between two parties. It is commonly used for user authentication and information exchange in web applications.
Backend Setup (ASP.NET Core)
We need to set up JWT authentication in the .NET Core backend. Start by installing the necessary NuGet packages for authentication:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Next, update the Startup.cs file to configure authentication:
public void ConfigureServices(IServiceCollection services) { // JWT Authentication Configuration services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidIssuer = Configuration["Jwt:Issuer"], ValidAudience = Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) }; }); services.AddControllers(); }
Now, let's create a method to generate JWT tokens after user login:
public class AuthController : ControllerBase { private readonly IConfiguration _configuration; public AuthController(IConfiguration configuration) { _configuration = configuration; } [HttpPost("login")] public IActionResult Login([FromBody] UserLoginModel loginModel) { // Validate user credentials (from database, for example) if (loginModel.Username == "admin" && loginModel.Password == "password") { var token = GenerateJwtToken(loginModel.Username); return Ok(new { Token = token }); } return Unauthorized(); } private string GenerateJwtToken(string username) { var claims = new[] { new Claim(ClaimTypes.Name, username) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } }
Frontend Setup (Angular)
On the frontend, use Angular to send the login credentials and store the JWT token for further use in the application. First, create an authentication service to handle login requests:
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; import { environment } from 'src/environments/environment'; @Injectable({ providedIn: 'root' }) export class AuthService { private apiUrl = environment.apiUrl; constructor(private http: HttpClient) { } login(username: string, password: string): Observable<any> { return this.http.post(`${this.apiUrl}/auth/login`, { username, password }); } getToken(): string { return localStorage.getItem('token'); } setToken(token: string): void { localStorage.setItem('token', token); } isAuthenticated(): boolean { const token = this.getToken(); return token && !this.isTokenExpired(token); } private isTokenExpired(token: string): boolean { const decodedToken = JSON.parse(atob(token.split('.')[1])); return decodedToken.exp < Date.now() / 1000; } }
Use this service to authenticate users and store the JWT token in local storage for subsequent API calls.
Task Prioritization
Task prioritization allows users to mark tasks with different priority levels, such as Low, Medium, and High. Let’s implement this feature in both the backend and frontend.
Backend Setup
Update the Task model to include a Priority field:
public class Task { public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } public bool IsCompleted { get; set; } public string Priority { get; set; } // Low, Medium, High }
Now, update the TaskController to accept priority levels when creating or updating a task.
[HttpPost] public async Task<ActionResult<Task>> CreateTask(Task task) { _context.Tasks.Add(task); await _context.SaveChangesAsync(); return CreatedAtAction(nameof(GetTasks), new { id = task.Id }, task); } [HttpPut("{id}")] public async Task<IActionResult> UpdateTask(int id, Task task) { if (id != task.Id) { return BadRequest(); } _context.Entry(task).State = EntityState.Modified; await _context.SaveChangesAsync(); return NoContent(); }
Frontend Setup
On the Angular frontend, provide a dropdown to select the priority level for a task:
<select [(ngModel)]="task.priority"> <option value="Low">Low</option> <option value="Medium">Medium</option> <option value="High">High</option> </select>
Update the task model and pass the priority field in the API request when creating or updating tasks.
File Uploads
To allow users to attach files to tasks, we will implement file upload functionality. This is useful for attaching documents, images, or other files related to a task.
Backend Setup
First, create an API endpoint for file uploads in the TaskController:
[HttpPost("upload/{taskId}")] public async Task<IActionResult> UploadFile(int taskId, IFormFile file) { if (file == null || file.Length == 0) { return BadRequest("No file uploaded."); } var filePath = Path.Combine(_env.WebRootPath, "uploads", file.FileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } var task = await _context.Tasks.FindAsync(taskId); if (task == null) { return NotFound(); } task.FilePath = filePath; _context.Tasks.Update(task); await _context.SaveChangesAsync(); return Ok(new { FilePath = filePath }); }
Frontend Setup
On the Angular side, create a file input field in the task creation form:
<input type="file" (change)="onFileSelect($event)" /> <button (click)="uploadFile()">Upload</button>
In the component, handle the file selection and upload:
onFileSelect(event: any): void { this.selectedFile = event.target.files[0]; } uploadFile(): void { const formData = new FormData(); formData.append('file', this.selectedFile, this.selectedFile.name); this.taskService.uploadFile(this.task.id, formData).subscribe(response => { console.log('File uploaded:', response); }); }
Real-time Notifications
Implementing real-time notifications can improve the user experience. Using SignalR, you can push live updates to users whenever a task is updated or a new task is created.
Backend Setup
Install the SignalR NuGet package:
dotnet add package Microsoft.AspNetCore.SignalR
Then, create a SignalR hub for real-time communication:
public class TaskNotificationHub : Hub { public async Task NotifyTaskUpdated(int taskId) { await Clients.All.SendAsync("ReceiveTaskUpdate", taskId); } }
Inject the SignalR hub into your task update API and call it when a task is updated:
public class TaskController : ControllerBase { private readonly IHubContext<TaskNotificationHub> _hubContext; public TaskController(IHubContext<TaskNotificationHub> hubContext) { _hubContext = hubContext; } [HttpPut("{id}")] public async Task<IActionResult> UpdateTask(int id, Task task) { // Update task logic await _hubContext.Clients.All.SendAsync("ReceiveTaskUpdate", task.Id); return NoContent(); } }
Frontend Setup
On the Angular side, use the SignalR client to receive real-time updates:
import * as signalR from "@microsoft/signalr"; export class TaskService { private connection: signalR.HubConnection; constructor() { this.connection = new signalR.HubConnectionBuilder() .withUrl("http://localhost:5000/taskNotificationHub") .build(); this.connection.on("ReceiveTaskUpdate", (taskId: number) => { console.log(`Task with ID ${taskId} updated`); // Handle the real-time update }); this.connection.start().catch(err => console.error(err)); } }