Skip to content

Latest commit

 

History

History
692 lines (542 loc) · 19.5 KB

File metadata and controls

692 lines (542 loc) · 19.5 KB

Security Service

Overview

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.

Primary Responsibilities

What This Service DOES:

  • 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

What This Service DOES NOT DO:

  • 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

Service Communication

Publishes Events:

  • UserAuthenticated - When user successfully logs in
  • UserRegistered - When new user is created
  • UserLocked - When account is locked due to security violations
  • PasswordChanged - When user changes password
  • SecurityAlert - For suspicious activities or security violations

Subscribes to Events:

  • AccountCreated (from Account service) - To link user identity with accounts
  • TransactionAttempt (from Transaction service) - For fraud detection
  • MovementCreated (from Movement service) - For security monitoring

Synchronous Communication:

  • Incoming: All other services call Security for token validation
  • Outgoing: Calls Notification service for sending security alerts and confirmations

🎯 Service Architecture

Clean Architecture Layers

  • 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

Domain Boundaries

  • User identity and credentials
  • Authentication sessions
  • Security policies and roles
  • Password policies and validation

🏗️ Architecture

Clean Architecture Layers

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

🔧 Features

Authentication Features

  • 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 Management Features

  • User Registration: Account creation with email verification
  • Profile Management: User information updates
  • Password Reset: Secure password recovery flow
  • Account Lockout: Brute force protection

Security Features

  • Password Hashing: BCrypt password hashing
  • Rate Limiting: API endpoint protection
  • CORS Configuration: Cross-origin request handling
  • Security Headers: Implementation of security best practices

🔌 API Endpoints

Authentication Endpoints

POST /api/auth/register

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"
}

POST /api/auth/login

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"
  }
}

POST /api/auth/refresh

Refresh JWT token using refresh token.

Request Body:

{
  "token": "expired_jwt_token",
  "refreshToken": "valid_refresh_token"
}

POST /api/auth/forgot-password

Initiate password reset process.

Request Body:

{
  "email": "user@example.com"
}

POST /api/auth/reset-password

Reset password using reset token.

Request Body:

{
  "token": "reset_token",
  "email": "user@example.com",
  "newPassword": "NewSecurePassword123!"
}

🗄️ Data Model

ApplicationUser Entity

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; }
}

Database Schema

  • AspNetUsers: User accounts and profiles
  • AspNetUserTokens: JWT refresh tokens
  • AspNetUserLogins: External login providers
  • AspNetRoles: User roles and permissions
  • AspNetUserRoles: User-role relationships

⚙️ Configuration

appsettings.json

{
  "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
  }
}

Environment Variables

# 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/"

🔐 Security Implementation

Password Hashing

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);
    }
}

JWT Token Generation

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
        };
    }
}

🧪 Testing

Unit Tests

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);
    }
}

Integration Tests

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;
    }
}

📊 Monitoring & Observability

Metrics

  • Authentication success/failure rates
  • Token generation frequency
  • Password reset requests
  • Failed login attempts
  • User registration trends

Logging Events

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");
}

Health Checks

  • Database connectivity
  • JWT key availability
  • External service dependencies
  • Identity provider status

🚀 Deployment

Docker Configuration

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"]

Azure Container Apps Configuration

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

🔧 Development Setup

Prerequisites

  • .NET 9 SDK
  • SQL Server or SQL Server Express
  • Visual Studio 2022 or VS Code

Local Development

  1. Update connection string in appsettings.Development.json
  2. Run database migrations:
    dotnet ef database update --project Security.Infrastructure --startup-project Security.Api
  3. Start the service:
    dotnet run --project Security.Api
  4. Access Scalar UI: https://localhost:5001/scalar

Database Migrations

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.Api

For more detailed Entity Framework documentation, see Entity Framework Core Guidelines.

🤝 Contributing

  1. Follow the Clean Architecture principles
  2. Implement comprehensive unit tests
  3. Follow security best practices
  4. Update API documentation
  5. Follow conventional commit messages

📚 Additional Resources