fengling-console/docs/task-04-create-auth-controller.md

5.0 KiB

Task 4: Create Auth Controller

Task Description

Files:

  • Create: src/Fengling.AuthService/Controllers/AuthController.cs
  • Create: src/Fengling.AuthService/DTOs/LoginRequest.cs
  • Create: src/Fengling.AuthService/DTOs/LoginResponse.cs
  • Create: src/Fengling.AuthService/DTOs/TokenResponse.cs

Implementation Steps

Step 1: Create DTOs

Create: src/Fengling.AuthService/DTOs/LoginRequest.cs

namespace Fengling.AuthService.DTOs;

public class LoginRequest
{
    public string UserName { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
    public long TenantId { get; set; }
}

Create: src/Fengling.AuthService/DTOs/LoginResponse.cs

namespace Fengling.AuthService.DTOs;

public class LoginResponse
{
    public string AccessToken { get; set; } = string.Empty;
    public string RefreshToken { get; set; } = string.Empty;
    public int ExpiresIn { get; set; }
    public string TokenType { get; set; } = "Bearer";
}

Step 2: Create AuthController

Create: src/Fengling.AuthService/Controllers/AuthController.cs

using Fengling.AuthService.DTOs;
using Fengling.AuthService.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace Fengling.AuthService.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly IOpenIddictApplicationManager _applicationManager;
    private readonly IOpenIddictAuthorizationManager _authorizationManager;
    private readonly IOpenIddictScopeManager _scopeManager;
    private readonly ILogger<AuthController> _logger;

    public AuthController(
        SignInManager<ApplicationUser> signInManager,
        UserManager<ApplicationUser> userManager,
        IOpenIddictApplicationManager applicationManager,
        IOpenIddictAuthorizationManager authorizationManager,
        IOpenIddictScopeManager scopeManager,
        ILogger<AuthController> logger)
    {
        _signInManager = signInManager;
        _userManager = userManager;
        _applicationManager = applicationManager;
        _authorizationManager = authorizationManager;
        _scopeManager = scopeManager;
        _logger = logger;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        var user = await _userManager.FindByNameAsync(request.UserName);
        if (user == null || user.IsDeleted)
        {
            return Unauthorized(new { error = "用户不存在" });
        }

        if (user.TenantId != request.TenantId)
        {
            return Unauthorized(new { error = "租户不匹配" });
        }

        var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);
        if (!result.Succeeded)
        {
            return Unauthorized(new { error = "用户名或密码错误" });
        }

        var token = await GenerateTokenAsync(user);
        return Ok(token);
    }

    private async Task<LoginResponse> GenerateTokenAsync(ApplicationUser user)
    {
        var claims = new List<System.Security.Claims.Claim>
        {
            new(Claims.Subject, user.Id.ToString()),
            new(Claims.Name, user.UserName ?? string.Empty),
            new(Claims.Email, user.Email ?? string.Empty),
            new("tenant_id", user.TenantId.ToString())
        };

        var roles = await _userManager.GetRolesAsync(user);
        foreach (var role in roles)
        {
            claims.Add(new Claim(Claims.Role, role));
        }

        var identity = new System.Security.Claims.ClaimsIdentity(claims, "Server");
        var principal = new System.Security.Claims.ClaimsPrincipal(identity);

        return new LoginResponse
        {
            AccessToken = "token-placeholder",
            RefreshToken = "refresh-placeholder",
            ExpiresIn = 3600,
            TokenType = "Bearer"
        };
    }
}

Step 3: Run to verify controller compilation

Run:

dotnet build

Expected: Build succeeds

Step 4: Commit

git add src/Fengling.AuthService/Controllers/ src/Fengling.AuthService/DTOs/
git commit -m "feat(auth): add authentication controller with login endpoint"

Context

This task creates an authentication controller with a login endpoint. The login endpoint validates user credentials, checks tenant isolation, and generates JWT tokens with embedded tenant_id claims for multi-tenant routing.

Tech Stack: ASP.NET Core Controllers, OpenIddict

Verification

  • LoginRequest DTO created
  • LoginResponse DTO created
  • AuthController created with login endpoint
  • Tenant validation implemented
  • Build succeeds
  • Committed to git

Notes

  • Token generation is placeholder, will be replaced by OpenIddict in next task
  • Tenant validation ensures multi-tenant isolation