Implemented secure refresh token rotation with the following features: - RefreshToken domain entity with IsExpired(), IsRevoked(), IsActive(), Revoke() methods - IRefreshTokenService with token generation, rotation, and revocation - RefreshTokenService with SHA-256 hashing and token family tracking - RefreshTokenRepository for database operations - Database migration for refresh_tokens table with proper indexes - Updated LoginCommandHandler and RegisterTenantCommandHandler to return refresh tokens - Added POST /api/auth/refresh endpoint (token rotation) - Added POST /api/auth/logout endpoint (revoke single token) - Added POST /api/auth/logout-all endpoint (revoke all user tokens) - Updated JWT access token expiration to 15 minutes (from 60) - Refresh token expiration set to 7 days - Security features: token reuse detection, IP address tracking, user-agent logging Changes: - Domain: RefreshToken.cs, IRefreshTokenRepository.cs - Application: IRefreshTokenService.cs, updated LoginResponseDto and RegisterTenantResult - Infrastructure: RefreshTokenService.cs, RefreshTokenRepository.cs, RefreshTokenConfiguration.cs - API: AuthController.cs (3 new endpoints), RefreshTokenRequest.cs, LogoutRequest.cs - Configuration: appsettings.Development.json (updated JWT settings) - DI: DependencyInjection.cs (registered new services) - Migration: AddRefreshTokens migration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
148 lines
5.4 KiB
PowerShell
148 lines
5.4 KiB
PowerShell
# Day 4 Authentication Flow Test Script
|
|
# Test JWT Service, Password Hashing, and Authentication Middleware
|
|
|
|
$baseUrl = "http://localhost:5000/api"
|
|
|
|
Write-Host "====================================" -ForegroundColor Cyan
|
|
Write-Host "Day 4: Authentication Flow Test" -ForegroundColor Cyan
|
|
Write-Host "====================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# Test 1: Register Tenant (should return JWT token)
|
|
Write-Host "Test 1: Register Tenant with Hashed Password" -ForegroundColor Yellow
|
|
$registerBody = @{
|
|
tenantName = "Test Corp"
|
|
tenantSlug = "test-corp-" + (Get-Random -Maximum 10000)
|
|
subscriptionPlan = "Professional"
|
|
adminEmail = "admin@testcorp.com"
|
|
adminPassword = "Admin@1234"
|
|
adminFullName = "Test Admin"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$registerResponse = Invoke-RestMethod -Uri "$baseUrl/tenants/register" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Body $registerBody
|
|
|
|
Write-Host "✓ Tenant registered successfully" -ForegroundColor Green
|
|
Write-Host " Tenant Slug: $($registerResponse.tenant.slug)" -ForegroundColor Gray
|
|
Write-Host " Admin Email: $($registerResponse.user.email)" -ForegroundColor Gray
|
|
Write-Host " Access Token (first 50 chars): $($registerResponse.accessToken.Substring(0, [Math]::Min(50, $registerResponse.accessToken.Length)))..." -ForegroundColor Gray
|
|
|
|
$token = $registerResponse.accessToken
|
|
$tenantSlug = $registerResponse.tenant.slug
|
|
$email = $registerResponse.user.email
|
|
} catch {
|
|
Write-Host "✗ Registration failed: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# Test 2: Login with hashed password verification
|
|
Write-Host "Test 2: Login with Password Verification" -ForegroundColor Yellow
|
|
$loginBody = @{
|
|
tenantSlug = $tenantSlug
|
|
email = $email
|
|
password = "Admin@1234"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$loginResponse = Invoke-RestMethod -Uri "$baseUrl/auth/login" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Body $loginBody
|
|
|
|
Write-Host "✓ Login successful" -ForegroundColor Green
|
|
Write-Host " User ID: $($loginResponse.user.id)" -ForegroundColor Gray
|
|
Write-Host " Tenant ID: $($loginResponse.tenant.id)" -ForegroundColor Gray
|
|
Write-Host " Access Token (first 50 chars): $($loginResponse.accessToken.Substring(0, [Math]::Min(50, $loginResponse.accessToken.Length)))..." -ForegroundColor Gray
|
|
|
|
$loginToken = $loginResponse.accessToken
|
|
} catch {
|
|
Write-Host "✗ Login failed: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# Test 3: Access protected endpoint without token (should fail)
|
|
Write-Host "Test 3: Access Protected Endpoint WITHOUT Token" -ForegroundColor Yellow
|
|
try {
|
|
$response = Invoke-RestMethod -Uri "$baseUrl/auth/me" `
|
|
-Method Get `
|
|
-ErrorAction Stop
|
|
|
|
Write-Host "✗ Should have failed but succeeded!" -ForegroundColor Red
|
|
} catch {
|
|
if ($_.Exception.Response.StatusCode -eq 401) {
|
|
Write-Host "✓ Correctly rejected (401 Unauthorized)" -ForegroundColor Green
|
|
} else {
|
|
Write-Host "✗ Unexpected error: $($_.Exception.Response.StatusCode)" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# Test 4: Access protected endpoint with valid token (should succeed)
|
|
Write-Host "Test 4: Access Protected Endpoint WITH Token" -ForegroundColor Yellow
|
|
try {
|
|
$headers = @{
|
|
"Authorization" = "Bearer $loginToken"
|
|
}
|
|
|
|
$meResponse = Invoke-RestMethod -Uri "$baseUrl/auth/me" `
|
|
-Method Get `
|
|
-Headers $headers
|
|
|
|
Write-Host "✓ Successfully accessed protected endpoint" -ForegroundColor Green
|
|
Write-Host " User ID: $($meResponse.userId)" -ForegroundColor Gray
|
|
Write-Host " Tenant ID: $($meResponse.tenantId)" -ForegroundColor Gray
|
|
Write-Host " Email: $($meResponse.email)" -ForegroundColor Gray
|
|
Write-Host " Full Name: $($meResponse.fullName)" -ForegroundColor Gray
|
|
Write-Host " Tenant Slug: $($meResponse.tenantSlug)" -ForegroundColor Gray
|
|
} catch {
|
|
Write-Host "✗ Failed to access protected endpoint: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# Test 5: Login with wrong password (should fail)
|
|
Write-Host "Test 5: Login with Wrong Password" -ForegroundColor Yellow
|
|
$wrongPasswordBody = @{
|
|
tenantSlug = $tenantSlug
|
|
email = $email
|
|
password = "WrongPassword123"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$response = Invoke-RestMethod -Uri "$baseUrl/auth/login" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Body $wrongPasswordBody `
|
|
-ErrorAction Stop
|
|
|
|
Write-Host "✗ Should have failed but succeeded!" -ForegroundColor Red
|
|
} catch {
|
|
if ($_.Exception.Response.StatusCode -eq 401) {
|
|
Write-Host "✓ Correctly rejected wrong password (401 Unauthorized)" -ForegroundColor Green
|
|
} else {
|
|
Write-Host "✗ Unexpected error: $($_.Exception.Response.StatusCode)" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "====================================" -ForegroundColor Cyan
|
|
Write-Host "All Authentication Tests Completed!" -ForegroundColor Cyan
|
|
Write-Host "====================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
Write-Host "Summary:" -ForegroundColor Yellow
|
|
Write-Host "✓ JWT Token Generation" -ForegroundColor Green
|
|
Write-Host "✓ Password Hashing (BCrypt)" -ForegroundColor Green
|
|
Write-Host "✓ Password Verification" -ForegroundColor Green
|
|
Write-Host "✓ JWT Authentication Middleware" -ForegroundColor Green
|
|
Write-Host "✓ Protected Endpoint Access Control" -ForegroundColor Green
|
|
Write-Host ""
|