今天的 Authorization 負責確認「你可以存取哪些資料」。
使用昨日專案
namespace TaskFlow3.Api.Models
{
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; } = "";
public bool IsCompleted { get; set; }
// Day 21:任務分類
public int? CategoryId { get; set; }
public Category? Category { get; set; }
// Day 22:原本的 AppUser 關聯
public int? UserId { get; set; }
public AppUser? User { get; set; }
// Day 25:Identity 帳號擁有者
public string? OwnerIdentityId { get; set; }
}
}
檢查編譯:dotnet build
建立 Migration:dotnet ef migrations add AddTaskOwnership --context TaskFlowDbContext
更新資料庫:dotnet ef database update --context TaskFlowDbContext
using TaskFlow3.Api.Models;
namespace TaskFlow3.Api.Services
{
public interface ITaskService
{
Task<List<TaskItem>> GetAllAsync(
string ownerId,
bool? completed = null,
string? keyword = null,
string sort = "asc"
);
Task<TaskItem?> GetByIdAsync(
int id,
string ownerId
);
Task<TaskItem> CreateAsync(
string title,
string ownerId
);
Task<bool> UpdateAsync(
int id,
string ownerId,
string title,
bool isCompleted
);
Task<bool> DeleteAsync(
int id,
string ownerId
);
}
}
using Microsoft.EntityFrameworkCore;
using TaskFlow3.Api.Data;
using TaskFlow3.Api.Models;
namespace TaskFlow3.Api.Services
{
public class TaskService : ITaskService
{
private readonly TaskFlowDbContext _db;
public TaskService(TaskFlowDbContext db)
{
_db = db;
}
// 查看目前使用者的任務
public async Task<List<TaskItem>> GetAllAsync(
string ownerId,
bool? completed = null,
string? keyword = null,
string sort = "asc")
{
IQueryable<TaskItem> query = _db.Tasks
.AsNoTracking()
.Where(t => t.OwnerIdentityId == ownerId);
// 完成狀態篩選
if (completed.HasValue)
{
query = query.Where(
t => t.IsCompleted == completed.Value
);
}
// 關鍵字搜尋
if (!string.IsNullOrWhiteSpace(keyword))
{
string searchText = keyword.Trim();
query = query.Where(
t => t.Title.Contains(searchText)
);
}
// 排序
if (string.Equals(
sort,
"desc",
StringComparison.OrdinalIgnoreCase))
{
query = query.OrderByDescending(t => t.Id);
}
else
{
query = query.OrderBy(t => t.Id);
}
return await query.ToListAsync();
}
// 查看指定任務
public async Task<TaskItem?> GetByIdAsync(
int id,
string ownerId)
{
return await _db.Tasks
.FirstOrDefaultAsync(
t => t.Id == id &&
t.OwnerIdentityId == ownerId
);
}
// 新增自己的任務
public async Task<TaskItem> CreateAsync(
string title,
string ownerId)
{
TaskItem task = new TaskItem
{
Title = title,
IsCompleted = false,
OwnerIdentityId = ownerId
};
_db.Tasks.Add(task);
await _db.SaveChangesAsync();
return task;
}
// 修改自己的任務
public async Task<bool> UpdateAsync(
int id,
string ownerId,
string title,
bool isCompleted)
{
TaskItem? task = await _db.Tasks
.FirstOrDefaultAsync(
t => t.Id == id &&
t.OwnerIdentityId == ownerId
);
if (task == null)
{
return false;
}
task.Title = title;
task.IsCompleted = isCompleted;
await _db.SaveChangesAsync();
return true;
}
// 刪除自己的任務
public async Task<bool> DeleteAsync(
int id,
string ownerId)
{
TaskItem? task = await _db.Tasks
.FirstOrDefaultAsync(
t => t.Id == id &&
t.OwnerIdentityId == ownerId
);
if (task == null)
{
return false;
}
_db.Tasks.Remove(task);
await _db.SaveChangesAsync();
return true;
}
}
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using TaskFlow3.Api.DTOs;
using TaskFlow3.Api.Models;
using TaskFlow3.Api.Services;
namespace TaskFlow3.Api.Controllers
{
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class TasksController : ControllerBase
{
private readonly ITaskService _taskService;
public TasksController(ITaskService taskService)
{
_taskService = taskService;
}
// 從已驗證身分取得登入者 ID
private string? GetOwnerId()
{
return User.FindFirstValue(
ClaimTypes.NameIdentifier
);
}
// GET /api/tasks
[HttpGet]
public async Task<ActionResult<List<TaskDto>>> GetTasks(
[FromQuery] bool? completed = null,
[FromQuery] string? keyword = null,
[FromQuery] string sort = "asc")
{
string? ownerId = GetOwnerId();
if (string.IsNullOrEmpty(ownerId))
{
return Unauthorized();
}
var tasks = await _taskService.GetAllAsync(
ownerId,
completed,
keyword,
sort
);
var result = tasks.Select(ToDto).ToList();
return Ok(result);
}
// GET /api/tasks/1
[HttpGet("{id:int}")]
public async Task<ActionResult<TaskDto>> GetTask(int id)
{
string? ownerId = GetOwnerId();
if (string.IsNullOrEmpty(ownerId))
{
return Unauthorized();
}
TaskItem? task = await _taskService.GetByIdAsync(
id,
ownerId
);
if (task == null)
{
return NotFound("找不到這筆任務");
}
return Ok(ToDto(task));
}
// POST /api/tasks
[HttpPost]
public async Task<ActionResult<TaskDto>> CreateTask(
[FromBody] CreateTaskDto input)
{
string? ownerId = GetOwnerId();
if (string.IsNullOrEmpty(ownerId))
{
return Unauthorized();
}
if (string.IsNullOrWhiteSpace(input.Title))
{
ModelState.AddModelError(
nameof(input.Title),
"任務名稱不可空白"
);
return ValidationProblem(ModelState);
}
string title = input.Title.Trim();
if (title.Length < 2 || title.Length > 100)
{
ModelState.AddModelError(
nameof(input.Title),
"任務名稱必須介於 2 到 100 個字元"
);
return ValidationProblem(ModelState);
}
TaskItem task = await _taskService.CreateAsync(
title,
ownerId
);
return CreatedAtAction(
nameof(GetTask),
new { id = task.Id },
ToDto(task)
);
}
// PUT /api/tasks/1
[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateTask(
int id,
[FromBody] UpdateTaskDto input)
{
string? ownerId = GetOwnerId();
if (string.IsNullOrEmpty(ownerId))
{
return Unauthorized();
}
if (string.IsNullOrWhiteSpace(input.Title))
{
ModelState.AddModelError(
nameof(input.Title),
"任務名稱不可空白"
);
return ValidationProblem(ModelState);
}
string title = input.Title.Trim();
if (title.Length < 2 || title.Length > 100)
{
ModelState.AddModelError(
nameof(input.Title),
"任務名稱必須介於 2 到 100 個字元"
);
return ValidationProblem(ModelState);
}
bool success = await _taskService.UpdateAsync(
id,
ownerId,
title,
input.IsCompleted
);
if (!success)
{
return NotFound("找不到這筆任務");
}
return NoContent();
}
// DELETE /api/tasks/1
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteTask(int id)
{
string? ownerId = GetOwnerId();
if (string.IsNullOrEmpty(ownerId))
{
return Unauthorized();
}
bool success = await _taskService.DeleteAsync(
id,
ownerId
);
if (!success)
{
return NotFound("找不到這筆任務");
}
return NoContent();
}
// Model 轉 DTO
private static TaskDto ToDto(TaskItem task)
{
return new TaskDto
{
Id = task.Id,
Title = task.Title,
IsCompleted = task.IsCompleted
};
}
}
}
在 VS Code 終端機執行:dotnet build
再啟動:dotnet run
在第二個 PowerShell 終端機設定:$base = "http://localhost:5130"
測試 1:沒有登入
try {
Invoke-WebRequest -Uri "$base/api/tasks" -UseBasicParsing
}
catch {
Write-Host "HTTP 狀態碼:" ([int]$_.Exception.Response.StatusCode)
}

登入 Alice:
$login = @{
email = "student@example.com"
password = "DemoOnly_2026!"
} | ConvertTo-Json
Invoke-RestMethod `
-Uri "$base/auth/login?useCookies=true" `
-Method Post `
-ContentType "application/json" `
-Body $login `
-SessionVariable aliceSession
新增 Alice 的任務:
$body = @{
title = "Alice 的 C# 作業"
} | ConvertTo-Json
$created = Invoke-RestMethod `
-Uri "$base/api/tasks" `
-Method Post `
-ContentType "application/json" `
-Body $body `
-WebSession $aliceSession
$created

接著查看自己的任務:
Invoke-RestMethod `
-Uri "$base/api/tasks" `
-WebSession $aliceSession
登入 Bob:
$bobLogin = @{
email = "bob@example.com"
password = "DemoOnly_2026!"
} | ConvertTo-Json
Invoke-RestMethod `
-Uri "$base/auth/login?useCookies=true" `
-Method Post `
-ContentType "application/json" `
-Body $bobLogin `
-SessionVariable bobSession

以 Bob 的登入狀態查詢 Alice 剛新增的任務:
Invoke-RestMethod `
-Uri "$base/api/tasks/$($created.id)" `
-WebSession $bobSession