The Security Service is responsible for authentication, authorization, and security management in the Bank System Microservices architecture. It acts as the central security authority, managing user identities, JWT tokens, role-based permissions, and security policies across all microservices.
- User Authentication: Login, logout, token validation, password management
- Authorization Management: Role-based access control (RBAC), permission management
- JWT Token Management: Token generation, validation, refresh, revocation
- User Identity Management: User registration, profile management, account status
- Security Policies: Password policies, account lockout, session management
- Audit Logging: Security events, login attempts, permission changes
- Multi-Factor Authentication (MFA): Setup and validation of 2FA/MFA
- Security Monitoring: Failed login detection, suspicious activity alerts
- Business Logic: Does not handle account balances, transactions, or financial operations
- Data Storage: Does not store business data (accounts, transactions, movements)
- Notifications: Does not send emails/SMS (delegates to Notification service)
- Reporting: Does not generate business reports (delegates to Reporting service)
- Direct Database Access: Does not access other services' databases directly
UserAuthenticated- When user successfully logs inUserRegistered- When new user is createdUserLocked- When account is locked due to security violationsPasswordChanged- When user changes passwordSecurityAlert- For suspicious activities or security violations
AccountCreated(from Account service) - To link user identity with accountsTransactionAttempt(from Transaction service) - For fraud detectionMovementCreated(from Movement service) - For security monitoring
- Incoming: All other services call Security for token validation
- Outgoing: Calls Notification service for sending security alerts and confirmations
- User Authentication: Secure login with JWT token generation
- User Registration: New user account creation with validation
- Password Management: Password reset and recovery functionality
- Authorization: Role-based access control (RBAC)
- Token Management: JWT token validation and refresh
- User Profile Management: User information updates
- User identity and credentials
- Authentication sessions
- Security policies and roles
- Password policies and validation
Security.Api/ # Presentation Layer
├── Controllers/ # API Controllers
├── Middleware/ # Authentication middleware
├── Extensions/ # Service extensions
└── Program.cs # Application startup
Security.Application/ # Application Layer
├── Commands/ # CQRS Commands (Register, Login, ResetPassword)
├── Queries/ # CQRS Queries (GetUser, ValidateToken)
├── Handlers/ # Command & Query Handlers
├── DTOs/ # Data Transfer Objects
├── Interfaces/ # Application Interfaces
├── Validators/ # FluentValidation Validators
└── Mappers/ # AutoMapper Profiles
Security.Domain/ # Domain Layer
├── Entities/ # Domain Entities (ApplicationUser)
├── ValueObjects/ # Value Objects (Email, Password)
├── Events/ # Domain Events
├── Enums/ # Domain Enumerations
└── Exceptions/ # Domain Exceptions
Security.Infrastructure/ # Infrastructure Layer
├── Data/ # EF Core DbContext
├── Services/ # External Service Integrations
├── Identity/ # ASP.NET Core Identity Configuration
└── Repositories/ # Repository Implementations
- JWT Token Authentication: Secure token-based authentication
- Multi-Factor Authentication: Optional 2FA support
- Session Management: Token expiration and refresh
- Password Policies: Configurable password complexity requirements
- User Registration: Account creation with email verification
- Profile Management: User information updates
- Password Reset: Secure password recovery flow
- Account Lockout: Brute force protection
- Password Hashing: BCrypt password hashing
- Rate Limiting: API endpoint protection
- CORS Configuration: Cross-origin request handling
- Security Headers: Implementation of security best practices
Register a new user account.
Request Body:
{
"email": "user@example.com",
"password": "SecurePassword123!",
"firstName": "John",
"lastName": "Doe",
"phoneNumber": "+1234567890"
}Response:
{
"userId": "guid",
"email": "user@example.com",
"message": "User registered successfully"
}Authenticate user and receive JWT token.
Request Body:
{
"email": "user@example.com",
"password": "SecurePassword123!"
}Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "refresh_token_string",
"expiresAt": "2024-12-31T23:59:59Z",
"user": {
"id": "guid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe"
}
}Refresh JWT token using refresh token.
Request Body:
{
"token": "expired_jwt_token",
"refreshToken": "valid_refresh_token"
}Initiate password reset process.
Request Body:
{
"email": "user@example.com"
}Reset password using reset token.
Request Body:
{
"token": "reset_token",
"email": "user@example.com",
"newPassword": "NewSecurePassword123!"
}public class ApplicationUser : IdentityUser<Guid>
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public bool IsActive { get; set; } = true;
public DateTime? LastLoginAt { get; set; }
public int FailedLoginAttempts { get; set; }
public DateTime? LockoutEndDate { get; set; }
}- AspNetUsers: User accounts and profiles
- AspNetUserTokens: JWT refresh tokens
- AspNetUserLogins: External login providers
- AspNetRoles: User roles and permissions
- AspNetUserRoles: User-role relationships
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=BankSystem_Security;Trusted_Connection=true;"
},
"Jwt": {
"Key": "your-super-secret-jwt-key-here",
"Issuer": "https://localhost:5001",
"Audience": "bank-system-api",
"ExpiryInMinutes": 60,
"RefreshTokenExpiryInDays": 7
},
"PasswordPolicy": {
"RequiredLength": 8,
"RequireNonAlphanumeric": true,
"RequireLowercase": true,
"RequireUppercase": true,
"RequireDigit": true
},
"Lockout": {
"DefaultLockoutTimeSpan": "00:05:00",
"MaxFailedAccessAttempts": 5,
"AllowedForNewUsers": true
}
}# Database
CONNECTIONSTRINGS__DEFAULTCONNECTION="Server=localhost;Database=BankSystem_Security;..."
# JWT Configuration
JWT__KEY="your-super-secret-jwt-key"
JWT__ISSUER="https://your-api.com"
JWT__AUDIENCE="bank-system-api"
# External Services
AZURE__KEYVAULT__VAULTURL="https://your-keyvault.vault.azure.net/"public class PasswordHasher
{
public string HashPassword(string password)
{
return BCrypt.Net.BCrypt.HashPassword(password, 12);
}
public bool VerifyPassword(string password, string hashedPassword)
{
return BCrypt.Net.BCrypt.Verify(password, hashedPassword);
}
}public class TokenService : ITokenService
{
public async Task<TokenDto> GenerateTokenAsync(ApplicationUser user)
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
new(ClaimTypes.Email, user.Email!),
new(ClaimTypes.Name, $"{user.FirstName} {user.LastName}"),
new("jti", Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiryInMinutes),
signingCredentials: credentials
);
return new TokenDto
{
Token = new JwtSecurityTokenHandler().WriteToken(token),
RefreshToken = await GenerateRefreshTokenAsync(user),
ExpiresAt = token.ValidTo
};
}
}public class AuthServiceTests
{
private readonly AuthService _authService;
private readonly Mock<IUserRepository> _mockUserRepository;
private readonly Mock<IJwtTokenService> _mockTokenService;
public AuthServiceTests()
{
_mockUserRepository = new Mock<IUserRepository>();
_mockTokenService = new Mock<IJwtTokenService>();
_authService = new AuthService(_mockUserRepository.Object, _mockTokenService.Object);
}
[Fact]
public async Task AuthenticateAsync_ValidCredentials_ShouldReturnToken()
{
// Arrange
var email = "test@example.com";
var password = "ValidPassword123!";
var user = new ApplicationUser { Email = email, PasswordHash = BCrypt.Net.BCrypt.HashPassword(password) };
_mockUserRepository.Setup(r => r.GetByEmailAsync(email))
.ReturnsAsync(user);
_mockTokenService.Setup(t => t.GenerateTokenAsync(user))
.ReturnsAsync("valid-jwt-token");
// Act
var result = await _authService.AuthenticateAsync(email, password);
// Assert
Assert.True(result.IsSuccess);
Assert.NotNull(result.Value.AccessToken);
}
[Fact]
public async Task AuthenticateAsync_InvalidCredentials_ShouldReturnFailure()
{
// Arrange
var email = "test@example.com";
var password = "WrongPassword";
_mockUserRepository.Setup(r => r.GetByEmailAsync(email))
.ReturnsAsync((ApplicationUser)null);
// Act
var result = await _authService.AuthenticateAsync(email, password);
// Assert
Assert.False(result.IsSuccess);
Assert.Equal("Invalid credentials", result.Error);
}
[Theory]
[InlineData("")]
[InlineData("weak")]
[InlineData("NoNumbers!")]
[InlineData("nonumbers123")]
public void ValidatePassword_WeakPasswords_ShouldReturnFalse(string password)
{
// Act
var result = _authService.ValidatePassword(password);
// Assert
Assert.False(result);
}
[Fact]
public void ValidatePassword_StrongPassword_ShouldReturnTrue()
{
// Arrange
var password = "StrongPassword123!";
// Act
var result = _authService.ValidatePassword(password);
// Assert
Assert.True(result);
}
}public class AuthControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public AuthControllerIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
_client = _factory.CreateClient();
}
[Fact]
public async Task Login_ValidCredentials_ShouldReturnOkWithToken()
{
// Arrange
var loginRequest = new LoginRequest
{
Email = "test@example.com",
Password = "TestPassword123!"
};
// Act
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<LoginResponse>(content, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Assert.NotNull(result);
Assert.NotEmpty(result.AccessToken);
}
[Fact]
public async Task Register_ValidUser_ShouldReturnCreated()
{
// Arrange
var registerRequest = new RegisterRequest
{
Email = "newuser@example.com",
Password = "NewPassword123!",
FirstName = "Test",
LastName = "User"
};
// Act
var response = await _client.PostAsJsonAsync("/api/auth/register", registerRequest);
// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
}
[Fact]
public async Task GetProfile_WithValidToken_ShouldReturnUserProfile()
{
// Arrange
var token = await GetValidTokenAsync();
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// Act
var response = await _client.GetAsync("/api/auth/profile");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var profile = JsonSerializer.Deserialize<UserProfileResponse>(content, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Assert.NotNull(profile);
Assert.NotEmpty(profile.Email);
}
private async Task<string> GetValidTokenAsync()
{
var loginRequest = new LoginRequest
{
Email = "test@example.com",
Password = "TestPassword123!"
};
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<LoginResponse>(content, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return result.AccessToken;
}
}- Authentication success/failure rates
- Token generation frequency
- Password reset requests
- Failed login attempts
- User registration trends
public static class SecurityEvents
{
public static readonly EventId UserRegistered = new(1001, "UserRegistered");
public static readonly EventId UserLoggedIn = new(1002, "UserLoggedIn");
public static readonly EventId LoginFailed = new(1003, "LoginFailed");
public static readonly EventId PasswordReset = new(1004, "PasswordReset");
public static readonly EventId AccountLocked = new(1005, "AccountLocked");
}- Database connectivity
- JWT key availability
- External service dependencies
- Identity provider status
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["Security.Api/Security.Api.csproj", "Security.Api/"]
COPY ["Security.Application/Security.Application.csproj", "Security.Application/"]
COPY ["Security.Domain/Security.Domain.csproj", "Security.Domain/"]
COPY ["Security.Infrastructure/Security.Infrastructure.csproj", "Security.Infrastructure/"]
RUN dotnet restore "Security.Api/Security.Api.csproj"
COPY . .
WORKDIR "/src/Security.Api"
RUN dotnet build "Security.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Security.Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Security.Api.dll"]apiVersion: apps/v1
kind: Deployment
metadata:
name: security-service
spec:
replicas: 3
selector:
matchLabels:
app: security-service
template:
metadata:
labels:
app: security-service
spec:
containers:
- name: security-service
image: bankregistry.azurecr.io/security-service:latest
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: security-secrets
key: database-connection- .NET 9 SDK
- SQL Server or SQL Server Express
- Visual Studio 2022 or VS Code
- Update connection string in
appsettings.Development.json - Run database migrations:
dotnet ef database update --project Security.Infrastructure --startup-project Security.Api
- Start the service:
dotnet run --project Security.Api
- Access Scalar UI:
https://localhost:5001/scalar
Package Configuration: The Microsoft.EntityFrameworkCore.Design package is configured only in the Security.Api project to avoid conflicts.
All commands should be run from the src/services/Security/src/ directory.
# Add new migration
dotnet ef migrations add <MigrationName> --project Security.Infrastructure --startup-project Security.Api
# Update database
dotnet ef database update --project Security.Infrastructure --startup-project Security.Api
# List all migrations
dotnet ef migrations list --project Security.Infrastructure --startup-project Security.Api
# Remove last migration (only if not applied to database)
dotnet ef migrations remove --project Security.Infrastructure --startup-project Security.Api
# Generate SQL script for production deployment
dotnet ef migrations script --project Security.Infrastructure --startup-project Security.Api --output migration-script.sql
# Apply specific migration
dotnet ef database update <MigrationName> --project Security.Infrastructure --startup-project Security.ApiFor more detailed Entity Framework documentation, see Entity Framework Core Guidelines.
- Follow the Clean Architecture principles
- Implement comprehensive unit tests
- Follow security best practices
- Update API documentation
- Follow conventional commit messages