diff --git a/services/iam-service-net/src/IamService.API/Controllers/AuthController.cs b/services/iam-service-net/src/IamService.API/Controllers/AuthController.cs
index 911d2197..13724043 100644
--- a/services/iam-service-net/src/IamService.API/Controllers/AuthController.cs
+++ b/services/iam-service-net/src/IamService.API/Controllers/AuthController.cs
@@ -86,7 +86,7 @@ public class AuthController : ControllerBase
/// Cancellation token
/// Result of password change operation
[HttpPost("change-password")]
- [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+ [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(
Summary = "Change password",
Description = "Changes the password for the currently authenticated user. Requires current password verification.",
@@ -125,7 +125,7 @@ public class AuthController : ControllerBase
/// Cancellation token
/// Result of logout operation
[HttpPost("logout")]
- [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+ [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(
Summary = "Logout",
Description = "Logs out the current user and revokes all associated tokens.",
@@ -219,7 +219,7 @@ public class AuthController : ControllerBase
/// Cancellation token
/// QR code and recovery codes
[HttpPost("2fa/enable")]
- [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+ [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(
Summary = "Enable 2FA",
Description = "Initiates 2FA setup. Returns QR code and recovery codes. Must be verified with /2fa/verify.",
@@ -264,7 +264,7 @@ public class AuthController : ControllerBase
/// Cancellation token
/// Result of 2FA verification
[HttpPost("2fa/verify")]
- [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+ [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(
Summary = "Verify 2FA code",
Description = "Verifies the TOTP code and completes 2FA setup.",
@@ -304,7 +304,7 @@ public class AuthController : ControllerBase
/// Cancellation token
/// Result of disabling 2FA
[HttpPost("2fa/disable")]
- [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+ [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(
Summary = "Disable 2FA",
Description = "Disables 2FA for the current user. Requires verification with current 2FA code.",
@@ -438,7 +438,7 @@ public class AuthController : ControllerBase
/// Cancellation token
/// List of linked providers
[HttpGet("linked-accounts")]
- [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+ [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerOperation(
Summary = "Get linked accounts",
Description = "Returns list of external OAuth providers linked to current user's account.",
diff --git a/services/iam-service-net/src/IamService.API/Controllers/RolesController.cs b/services/iam-service-net/src/IamService.API/Controllers/RolesController.cs
index 43220a37..e118f6cd 100644
--- a/services/iam-service-net/src/IamService.API/Controllers/RolesController.cs
+++ b/services/iam-service-net/src/IamService.API/Controllers/RolesController.cs
@@ -17,7 +17,7 @@ namespace IamService.API.Controllers;
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/roles")]
-[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+[Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerTag("Role management endpoints - requires authentication")]
public class RolesController : ControllerBase
{
diff --git a/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs b/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
index f408af00..66ad5cd0 100644
--- a/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
+++ b/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
@@ -2,11 +2,13 @@ using Asp.Versioning;
using MediatR;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using IamService.API.Application.Common;
using IamService.API.Application.Commands.Users;
using IamService.API.Application.Queries.Users;
+using IamService.Domain.AggregatesModel.UserAggregate;
namespace IamService.API.Controllers;
@@ -17,19 +19,22 @@ namespace IamService.API.Controllers;
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/users")]
-[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
+[Authorize(AuthenticationSchemes = "Bearer")]
[SwaggerTag("User management endpoints - requires authentication")]
public class UsersController : ControllerBase
{
private readonly IMediator _mediator;
private readonly ILogger _logger;
+ private readonly UserManager _userManager;
public UsersController(
IMediator mediator,
- ILogger logger)
+ ILogger logger,
+ UserManager userManager)
{
_mediator = mediator;
_logger = logger;
+ _userManager = userManager;
}
///
@@ -238,6 +243,89 @@ public class UsersController : ControllerBase
Roles = roles
}));
}
+
+ ///
+ /// EN: Get user roles by user ID.
+ /// VI: Lấy danh sách roles của user theo ID.
+ ///
+ /// User ID
+ /// Cancellation token
+ /// List of user roles
+ [HttpGet("{id:guid}/roles")]
+ [SwaggerOperation(
+ Summary = "Get user roles",
+ Description = "Retrieves all roles assigned to a specific user.",
+ OperationId = "GetUserRoles")]
+ [SwaggerResponse(StatusCodes.Status200OK, "Successfully retrieved user roles", typeof(ApiResponse))]
+ [SwaggerResponse(StatusCodes.Status401Unauthorized, "Authentication required")]
+ [SwaggerResponse(StatusCodes.Status404NotFound, "User not found")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task GetUserRoles(
+ [FromRoute, SwaggerParameter("User ID", Required = true)] Guid id,
+ CancellationToken cancellationToken = default)
+ {
+ var user = await _userManager.FindByIdAsync(id.ToString());
+ if (user == null)
+ {
+ return NotFound(ApiResponse.Fail("USER_NOT_FOUND", $"User with ID {id} not found."));
+ }
+
+ var roles = await _userManager.GetRolesAsync(user);
+
+ return Ok(ApiResponse.Ok(new UserRolesDto
+ {
+ UserId = id.ToString(),
+ Roles = roles
+ }));
+ }
+
+ ///
+ /// EN: Get user permissions by user ID.
+ /// VI: Lấy danh sách permissions của user theo ID.
+ ///
+ /// User ID
+ /// Cancellation token
+ /// List of user permissions
+ [HttpGet("{id:guid}/permissions")]
+ [SwaggerOperation(
+ Summary = "Get user permissions",
+ Description = "Retrieves all permissions for a specific user (derived from roles).",
+ OperationId = "GetUserPermissions")]
+ [SwaggerResponse(StatusCodes.Status200OK, "Successfully retrieved user permissions", typeof(ApiResponse))]
+ [SwaggerResponse(StatusCodes.Status401Unauthorized, "Authentication required")]
+ [SwaggerResponse(StatusCodes.Status404NotFound, "User not found")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task GetUserPermissions(
+ [FromRoute, SwaggerParameter("User ID", Required = true)] Guid id,
+ CancellationToken cancellationToken = default)
+ {
+ var user = await _userManager.FindByIdAsync(id.ToString());
+ if (user == null)
+ {
+ return NotFound(ApiResponse.Fail("USER_NOT_FOUND", $"User with ID {id} not found."));
+ }
+
+ var roles = await _userManager.GetRolesAsync(user);
+ var claims = await _userManager.GetClaimsAsync(user);
+
+ // EN: Extract permissions from claims
+ // VI: Lấy permissions từ claims
+ var permissions = claims
+ .Where(c => c.Type == "permission")
+ .Select(c => c.Value)
+ .ToList();
+
+ return Ok(ApiResponse.Ok(new UserPermissionsDto
+ {
+ UserId = id.ToString(),
+ Roles = roles,
+ Permissions = permissions
+ }));
+ }
}
///
@@ -279,3 +367,47 @@ public class DeleteUserResult
///
public string Message { get; set; } = string.Empty;
}
+
+///
+/// EN: User roles response DTO.
+/// VI: DTO response cho danh sách roles của user.
+///
+public class UserRolesDto
+{
+ ///
+ /// EN: User ID.
+ /// VI: ID của user.
+ ///
+ public string UserId { get; set; } = string.Empty;
+
+ ///
+ /// EN: List of role names assigned to the user.
+ /// VI: Danh sách tên roles được gán cho user.
+ ///
+ public IEnumerable Roles { get; set; } = Enumerable.Empty();
+}
+
+///
+/// EN: User permissions response DTO.
+/// VI: DTO response cho danh sách permissions của user.
+///
+public class UserPermissionsDto
+{
+ ///
+ /// EN: User ID.
+ /// VI: ID của user.
+ ///
+ public string UserId { get; set; } = string.Empty;
+
+ ///
+ /// EN: List of role names assigned to the user.
+ /// VI: Danh sách tên roles được gán cho user.
+ ///
+ public IEnumerable Roles { get; set; } = Enumerable.Empty();
+
+ ///
+ /// EN: List of permission names assigned to the user.
+ /// VI: Danh sách tên permissions được gán cho user.
+ ///
+ public IEnumerable Permissions { get; set; } = Enumerable.Empty();
+}
diff --git a/services/iam-service-net/src/IamService.Infrastructure/DependencyInjection.cs b/services/iam-service-net/src/IamService.Infrastructure/DependencyInjection.cs
index 83831aac..2b361727 100644
--- a/services/iam-service-net/src/IamService.Infrastructure/DependencyInjection.cs
+++ b/services/iam-service-net/src/IamService.Infrastructure/DependencyInjection.cs
@@ -113,40 +113,26 @@ public static class DependencyInjection
.AddAspNetIdentity()
.AddDeveloperSigningCredential(); // EN: Use certificate in production / VI: Dùng certificate trong production
- // EN: Add JWT Bearer authentication for API endpoints
- // VI: Thêm JWT Bearer authentication cho API endpoints
+ // EN: Add JWT Bearer authentication for API endpoints using local IdentityServer
+ // VI: Thêm JWT Bearer authentication cho API endpoints sử dụng IdentityServer cục bộ
+ // EN: AddLocalApi() allows JWT validation without external metadata calls (for self-hosted IdentityServer)
+ // VI: AddLocalApi() cho phép JWT validation mà không cần gọi metadata bên ngoài (cho self-hosted IdentityServer)
services.AddAuthentication()
- .AddJwtBearer(options =>
+ .AddLocalApi("Bearer", options =>
{
- // EN: Configure to validate tokens from local IdentityServer
- // VI: Cấu hình để validate tokens từ IdentityServer cục bộ
- options.Authority = configuration["IdentityServer:Authority"] ?? "https://localhost:5001";
- options.Audience = "iam-api";
- options.RequireHttpsMetadata = false; // EN: Set to true in production / VI: Đặt true trong production
-
- // EN: Disable metadata validation for testing/development
- // VI: Tắt metadata validation cho testing/development
- options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
- {
- ValidateAudience = false, // EN: Disable for flexibility / VI: Tắt để linh hoạt
- ValidateIssuer = false, // EN: Disable for testing / VI: Tắt cho testing
- ValidateLifetime = true,
- RequireSignedTokens = true,
- ValidateIssuerSigningKey = true
- };
-
- // EN: Disable metadata address requirement if in testing environment
- // VI: Tắt yêu cầu metadata address nếu trong testing environment
- if (string.Equals(environmentName, "Testing", StringComparison.OrdinalIgnoreCase))
- {
- options.MetadataAddress = string.Empty;
- options.TokenValidationParameters.ValidateIssuerSigningKey = false;
- options.TokenValidationParameters.RequireSignedTokens = false;
- // EN: ASP.NET Core 8+ requires JsonWebToken, not JwtSecurityToken
- // VI: ASP.NET Core 8+ yêu cầu JsonWebToken, không phải JwtSecurityToken
- options.TokenValidationParameters.SignatureValidator = (token, parameters) => new Microsoft.IdentityModel.JsonWebTokens.JsonWebToken(token);
- }
+ options.ExpectedScope = "openid";
});
+
+ // EN: Configure authorization policies
+ // VI: Cấu hình authorization policies
+ services.AddAuthorization(options =>
+ {
+ options.AddPolicy("LocalApi", policy =>
+ {
+ policy.AddAuthenticationSchemes("Bearer");
+ policy.RequireAuthenticatedUser();
+ });
+ });
// EN: Register repositories
// VI: Đăng ký repositories