Skip to content

Commit f6ad20f

Browse files
add auth
1 parent 589ab02 commit f6ad20f

File tree

12 files changed

+248
-5
lines changed

12 files changed

+248
-5
lines changed

Controllers/AuthController.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using MediatR;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Mvc;
4+
using PaymentCoreServiceApi.Features.Auth;
5+
using PaymentCoreServiceApi.Features.Auth.Commands;
6+
7+
namespace PaymentCoreServiceApi.Controllers;
8+
9+
[ApiController]
10+
[Route("api/[controller]")]
11+
[Authorize] // Yêu cầu authentication mặc định cho controller
12+
public class AuthController : ControllerBase
13+
{
14+
private readonly IMediator _mediator;
15+
16+
public AuthController(IMediator mediator)
17+
{
18+
_mediator = mediator;
19+
}
20+
21+
[HttpPost("login")]
22+
[AllowAnonymous]
23+
public async Task<ActionResult<LoginResponse>> Login([FromBody] LoginCommand command)
24+
{
25+
try
26+
{
27+
var response = await _mediator.Send(command);
28+
return Ok(response);
29+
}
30+
catch (UnauthorizedAccessException)
31+
{
32+
return Unauthorized();
33+
}
34+
}
35+
}

Controllers/UsersController.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
using MediatR;
2-
using Microsoft.AspNetCore.Http.HttpResults;
2+
using Microsoft.AspNetCore.Authorization;
33
using Microsoft.AspNetCore.Mvc;
44
using PaymentCoreServiceApi.Features.Users.Commands;
55

66
namespace PaymentCoreServiceApi.Controllers;
77

88
[ApiController]
99
[Route("api/[controller]")]
10+
[Authorize] // Yêu cầu authentication cho tất cả các endpoints trong controller
1011
public class UsersController : ControllerBase
1112
{
1213
private readonly IMediator _mediator;
@@ -17,6 +18,7 @@ public UsersController(IMediator mediator)
1718
}
1819

1920
[HttpPost]
21+
[AllowAnonymous] // Cho phép tạo user mà không cần authentication
2022
public async Task<IActionResult> Create([FromBody] CreateUserCommand command)
2123
{
2224
var user = await _mediator.Send(command);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using MediatR;
2+
3+
namespace PaymentCoreServiceApi.Features.Auth.Commands;
4+
5+
public record LoginCommand : IRequest<LoginResponse>
6+
{
7+
public string UserName { get; init; } = default!;
8+
public string Password { get; init; } = default!;
9+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using MediatR;
2+
using Microsoft.EntityFrameworkCore;
3+
using PaymentCoreServiceApi.Infrastructure.DbContexts;
4+
5+
namespace PaymentCoreServiceApi.Features.Auth.Commands;
6+
7+
public class LoginCommandHandler : IRequestHandler<LoginCommand, LoginResponse>
8+
{
9+
private readonly AppDbContext _context;
10+
private readonly IJwtService _jwtService;
11+
12+
public LoginCommandHandler(AppDbContext context, IJwtService jwtService)
13+
{
14+
_context = context;
15+
_jwtService = jwtService;
16+
}
17+
18+
public async Task<LoginResponse> Handle(LoginCommand request, CancellationToken cancellationToken)
19+
{
20+
var user = await _context.Users
21+
.FirstOrDefaultAsync(u => u.UserName == request.UserName, cancellationToken);
22+
23+
if (user == null || user.Password != request.Password) // In production, use proper password hashing
24+
{
25+
throw new UnauthorizedAccessException("Invalid username or password");
26+
}
27+
28+
var token = _jwtService.GenerateToken(user);
29+
30+
return new LoginResponse
31+
{
32+
Token = token,
33+
RefreshToken = "", // Implement refresh token if needed
34+
Expiration = DateTime.Now.AddHours(1)
35+
};
36+
}
37+
}

Features/Auth/JwtService.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System.IdentityModel.Tokens.Jwt;
2+
using System.Security.Claims;
3+
using System.Text;
4+
using Microsoft.IdentityModel.Tokens;
5+
using PaymentCoreServiceApi.Core.Entities.UserAgents;
6+
7+
namespace PaymentCoreServiceApi.Features.Auth;
8+
9+
public interface IJwtService
10+
{
11+
string GenerateToken(User user);
12+
ClaimsPrincipal? ValidateToken(string token);
13+
}
14+
15+
public class JwtService : IJwtService
16+
{
17+
private readonly IConfiguration _configuration;
18+
19+
public JwtService(IConfiguration configuration)
20+
{
21+
_configuration = configuration;
22+
}
23+
24+
public string GenerateToken(User user)
25+
{
26+
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!));
27+
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
28+
29+
var claims = new[]
30+
{
31+
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
32+
new Claim(ClaimTypes.Name, user.UserName),
33+
new Claim(ClaimTypes.Email, user.Email),
34+
};
35+
36+
var token = new JwtSecurityToken(
37+
issuer: _configuration["Jwt:Issuer"],
38+
audience: _configuration["Jwt:Audience"],
39+
claims: claims,
40+
expires: DateTime.Now.AddHours(1),
41+
signingCredentials: credentials
42+
);
43+
44+
return new JwtSecurityTokenHandler().WriteToken(token);
45+
}
46+
47+
public ClaimsPrincipal? ValidateToken(string token)
48+
{
49+
try
50+
{
51+
var tokenHandler = new JwtSecurityTokenHandler();
52+
var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!);
53+
54+
var validationParameters = new TokenValidationParameters
55+
{
56+
ValidateIssuerSigningKey = true,
57+
IssuerSigningKey = new SymmetricSecurityKey(key),
58+
ValidateIssuer = true,
59+
ValidateAudience = true,
60+
ValidIssuer = _configuration["Jwt:Issuer"],
61+
ValidAudience = _configuration["Jwt:Audience"],
62+
ClockSkew = TimeSpan.Zero
63+
};
64+
65+
return tokenHandler.ValidateToken(token, validationParameters, out _);
66+
}
67+
catch
68+
{
69+
return null;
70+
}
71+
}
72+
}

Features/Auth/LoginModels.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace PaymentCoreServiceApi.Features.Auth;
2+
3+
public class LoginRequest
4+
{
5+
public string UserName { get; set; } = default!;
6+
public string Password { get; set; } = default!;
7+
}
8+
9+
public class LoginResponse
10+
{
11+
public string Token { get; set; } = default!;
12+
public string RefreshToken { get; set; } = default!;
13+
public DateTime Expiration { get; set; }
14+
}

Infrastructure/Extensions/ServiceCollectionExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
using System.Text;
2+
using Microsoft.AspNetCore.Authentication.JwtBearer;
3+
using Microsoft.Extensions.Configuration;
14
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.IdentityModel.Tokens;
26
using PaymentCoreServiceApi.Core.Interfaces.Repositories.Write;
7+
using PaymentCoreServiceApi.Features.Auth;
38
using PaymentCoreServiceApi.Infrastructure.Repositories.Write;
9+
using PaymentCoreServiceApi.Middlewares;
410

511
namespace PaymentCoreServiceApi.Infrastructure.Extensions;
612

@@ -16,4 +22,30 @@ public static IServiceCollection AddRepositories(this IServiceCollection service
1622

1723
return services;
1824
}
25+
26+
public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, IConfiguration configuration)
27+
{
28+
// Register JWT Services
29+
services.AddScoped<IJwtService, JwtService>();
30+
services.AddScoped<JwtMiddleware>();
31+
32+
// Configure JWT Authentication
33+
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
34+
.AddJwtBearer(options =>
35+
{
36+
options.TokenValidationParameters = new TokenValidationParameters
37+
{
38+
ValidateIssuer = true,
39+
ValidateAudience = true,
40+
ValidateLifetime = true,
41+
ValidateIssuerSigningKey = true,
42+
ValidIssuer = configuration["Jwt:Issuer"],
43+
ValidAudience = configuration["Jwt:Audience"],
44+
IssuerSigningKey = new SymmetricSecurityKey(
45+
Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!))
46+
};
47+
});
48+
49+
return services;
50+
}
1951
}

Middlewares/JwtMiddleware.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
1+
using PaymentCoreServiceApi.Features.Auth;
2+
13
namespace PaymentCoreServiceApi.Middlewares;
24

3-
public class JwtMiddleware
5+
public class JwtMiddleware : IMiddleware
46
{
5-
7+
private readonly IJwtService _jwtService;
8+
9+
public JwtMiddleware(IJwtService jwtService)
10+
{
11+
_jwtService = jwtService;
12+
}
13+
14+
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
15+
{
16+
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
17+
18+
if (token != null)
19+
{
20+
var principal = _jwtService.ValidateToken(token);
21+
if (principal != null)
22+
{
23+
context.User = principal;
24+
}
25+
}
26+
27+
await next(context);
28+
}
629
}

PaymentCoreServiceApi.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<ItemGroup>
1111
<PackageReference Include="MediatR" Version="10.0.0" />
1212
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
13+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
1314
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.17"/>
1415
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
1516
<PrivateAssets>all</PrivateAssets>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AISender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7a397d32a96844da8ee820e6e5ea0b32ba00_003F69_003F49fe15d6_003FISender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
23
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATransaction_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F590d595e5943477d8c452e8479e9200356800_003F82_003F7d745caa_003FTransaction_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

0 commit comments

Comments
 (0)