Changes between Version 21 and Version 22 of P9


Ignore:
Timestamp:
06/23/26 00:01:06 (4 days ago)
Author:
211099
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • P9

    v21 v22  
    767767
    768768Two new indexes were created for this scenario: idx_story_created_user and idx_genre_covering. Combined with indexes from previous scenarios, 8 indexes are used naturally in the execution plan without any forcing flags. Average execution time: 25.720 ms without indexes vs 25.813 ms with indexes. The indexes are kept and will deliver measurable improvements as data volume grows.
     769
     770
     771= Security measures
     772== Authentication and Authorization
     773===  User Authentication with JWT
     774ChapterX uses JSON Web Tokens (JWT) for authentication. The architecture is fully stateless so that means that no session is stored on the server between requests, so each request carries its own proof of identity in the token. After a successful login, the token is returned to the client in the JSON response body; the client is responsible for storing and attaching it to subsequent requests via the Authorization Bearer token header.
     775Login flow:
     776{{{
     777var user = await _userRepository.GetByEmailAsync(request.Email, cancellationToken)
     778    ?? throw new UnauthorizedAccessException("Invalid email or password.");
     779
     780if (!BCrypt.Net.BCrypt.Verify(request.Password, user.Password))
     781    throw new UnauthorizedAccessException("Invalid email or password.");
     782
     783var token = _jwtTokenService.GenerateToken(user);
     784return new LoginResponse(token, user.Id, user.Username, user.Email, user.Name, user.Surname, role);
     785}}}
     786The identical error message for both a missing user and a wrong password prevents email enumeration attacks, because an attacker cannot distinguish between the two failure modes.
     787=== Access Token
     788The access token is generated by JwtTokenService. It is a signed, self-contained token that embeds the user's identity and role directly in its payload, eliminating the need for a database lookup on every authenticated request.
     789Token generation:
     790{{{
     791public string GenerateToken(User user)
     792{
     793    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!));
     794    var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
     795
     796    var role = user.Admin != null ? "Admin"
     797        : user.Writer != null ? "Writer"
     798        : "RegularUser";
     799
     800    var claims = new[]
     801    {
     802        new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),   // subject: user ID
     803        new Claim(JwtRegisteredClaimNames.Email, user.Email),         // user email
     804        new Claim(JwtRegisteredClaimNames.UniqueName, user.Username), // username
     805        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // unique token ID
     806        new Claim(ClaimTypes.Role, role)                              // role claim
     807    };
     808
     809    var token = new JwtSecurityToken(
     810        issuer: _configuration["Jwt:Issuer"],
     811        audience: _configuration["Jwt:Audience"],
     812        claims: claims,
     813        expires: DateTime.UtcNow.AddDays(7),
     814        signingCredentials: credentials
     815    );
     816
     817    return new JwtSecurityTokenHandler().WriteToken(token);
     818}
     819}}}
     820The token is signed with HMAC-SHA256 using a symmetric key loaded from configuration. This ensures that any modification to the token payload after issuance will invalidate the signature, making it impossible for a client to forge or tamper with claims. The token expires after 7 days.
     821===  JWT Validation
     822ASP.NET Core's built-in JwtBearer middleware intercepts every incoming HTTP request and validates the token before any controller action runs.
     823{{{
     824builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
     825    .AddJwtBearer(options =>
     826    {
     827        options.TokenValidationParameters = new TokenValidationParameters
     828        {
     829            ValidateIssuer = true,
     830            ValidateAudience = true,
     831            ValidateLifetime = true,
     832            ValidateIssuerSigningKey = true,
     833            ValidIssuer = builder.Configuration["Jwt:Issuer"],
     834            ValidAudience = builder.Configuration["Jwt:Audience"],
     835            IssuerSigningKey = new SymmetricSecurityKey(
     836                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
     837        };
     838    });
     839}}}
     840=== Security Filter Chain
     841ASP.NET Core processes requests through an ordered middleware pipeline. Authentication and authorization are placed after CORS and exception handling, and before controller mapping, which means the token is validated on every request regardless of which controller handles it.
     842=== Role-Based Access Control
     843The system defines three roles, derived at login time from the user's related entities in the database and embedded as a ClaimTypes.Role claim in the JWT.
     844{{{
     845var role = user.Admin != null ? "Admin"
     846    : user.Writer != null ? "Writer"
     847    : "RegularUser";
     848}}}