# Task 5: Create OpenIddict Endpoints ## Task Description **Files:** - Create: `src/Fengling.AuthService/Controllers/AuthorizationController.cs` ## Implementation Steps ### Step 1: Create authorization endpoints Create: `src/Fengling.AuthService/Controllers/AuthorizationController.cs` ```csharp using Fengling.AuthService.Models; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; namespace Fengling.AuthService.Controllers; public class AuthorizationController : Controller { private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictAuthorizationManager _authorizationManager; private readonly IOpenIddictScopeManager _scopeManager; private readonly SignInManager _signInManager; private readonly UserManager _userManager; private readonly ILogger _logger; public AuthorizationController( IOpenIddictApplicationManager applicationManager, IOpenIddictAuthorizationManager authorizationManager, IOpenIddictScopeManager scopeManager, SignInManager signInManager, UserManager userManager, ILogger logger) { _applicationManager = applicationManager; _authorizationManager = authorizationManager; _scopeManager = scopeManager; _signInManager = signInManager; _userManager = userManager; _logger = logger; } [HttpPost("~/connect/token")] [Produces("application/json")] public async Task Exchange() { var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null || user.IsDeleted) { return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, new AuthenticationProperties(new Dictionary { [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户不存在" })); } var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); if (!result.Succeeded) { return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, new AuthenticationProperties(new Dictionary { [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户名或密码错误" })); } var principal = await _signInManager.CreateUserPrincipalAsync(user); var claims = new List { 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)); } principal.SetScopes(request.GetScopes()); principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, new AuthenticationProperties(new Dictionary { [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.UnsupportedGrantType, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "不支持的授权类型" })); } [HttpGet("~/connect/authorize")] [HttpPost("~/connect/authorize")] [IgnoreAntiforgeryToken] public async Task Authorize() { var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); var result = await HttpContext.AuthenticateAsync(); if (result == null || !result.Succeeded) { return Challenge( new AuthenticationProperties { RedirectUri = Request.Path + Request.QueryString }, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } return Ok(new { message = "Authorization endpoint" }); } [HttpPost("~/connect/logout")] [ValidateAntiForgeryToken] public async Task Logout() { await HttpContext.SignOutAsync(); return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } } ``` ### Step 2: Run to verify Run: ```bash dotnet run ``` Test with curl: ```bash curl -X POST http://localhost:5000/connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password" \ -d "username=admin" \ -d "password=Admin@123" \ -d "scope=api offline_access" ``` Expected: 400 error (user doesn't exist yet, but endpoint is working) ### Step 3: Commit ```bash git add src/Fengling.AuthService/Controllers/AuthorizationController.cs git commit -m "feat(auth): add OpenIddict authorization endpoints" ``` ## Context This task creates OpenIddict OAuth2/OIDC endpoints including token exchange, authorize, and logout. The token endpoint supports password flow for direct authentication. **Tech Stack**: OpenIddict 7.2.0, ASP.NET Core ## Verification - [ ] AuthorizationController created - [ ] Token endpoint supports password flow - [ ] Tenant ID added to token claims - [ ] Build succeeds - [ ] Committed to git ## Notes - Token endpoint returns JWT with tenant_id claim - Logout endpoint clears session