using Abp.Authorization; using Abp.Authorization.Users; using Abp.MultiTenancy; using Abp.Runtime.Security; using ASPBaseOIDC.Application.Authorization.ExternalAuth.Dto; using ASPBaseOIDC.Authentication.JwtBearer; using ASPBaseOIDC.Authorization; using ASPBaseOIDC.Authorization.ExternalAuth; using ASPBaseOIDC.Authorization.Users; using ASPBaseOIDC.Models.TokenAuth; using ASPBaseOIDC.MultiTenancy; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; namespace ASPBaseOIDC.Controllers { [Route("api/[controller]/[action]")] public class TokenAuthController : ASPBaseOIDCControllerBase { private readonly LogInManager _logInManager; private readonly ITenantCache _tenantCache; private readonly AbpLoginResultTypeHelper _abpLoginResultTypeHelper; private readonly TokenAuthConfiguration _configuration; private readonly ExternalAuthenticationManager _externalAuthManager; public TokenAuthController( LogInManager logInManager, ITenantCache tenantCache, AbpLoginResultTypeHelper abpLoginResultTypeHelper, TokenAuthConfiguration configuration, ExternalAuthenticationManager externalAuthManager) { _logInManager = logInManager; _tenantCache = tenantCache; _abpLoginResultTypeHelper = abpLoginResultTypeHelper; _configuration = configuration; _externalAuthManager = externalAuthManager; } [HttpPost] public async Task Authenticate([FromBody] AuthenticateModel model) { var loginResult = await GetLoginResultAsync( model.UserNameOrEmailAddress, model.Password, GetTenancyNameOrNull() ); var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)); return new AuthenticateResultModel { AccessToken = accessToken, EncryptedAccessToken = GetEncryptedAccessToken(accessToken), ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds, UserId = loginResult.User.Id }; } /// /// Authenticate with external OIDC/OAuth2 provider (Authentik, Keycloak, etc.) /// Passthrough approach: validates external token and returns it as-is /// [HttpPost] [AbpAllowAnonymous] public async Task AuthenticateExternal([FromBody] ExternalAuthModel model) { // Authenticate with external provider (validates token, provisions user if needed) var result = await _externalAuthManager.AuthenticateWithExternalTokenAsync( model.ProviderName, model.IdToken, AbpSession.TenantId ); // Return original external token (passthrough approach) return new AuthenticateResultModel { AccessToken = result.AccessToken, // Passthrough external token EncryptedAccessToken = GetEncryptedAccessToken(result.AccessToken), ExpireInSeconds = result.ExpiresIn, UserId = result.User.Id }; } private string GetTenancyNameOrNull() { if (!AbpSession.TenantId.HasValue) { return null; } return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName; } private async Task> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName) { var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName); switch (loginResult.Result) { case AbpLoginResultType.Success: return loginResult; default: throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName); } } private string CreateAccessToken(IEnumerable claims, TimeSpan? expiration = null) { var now = DateTime.UtcNow; var jwtSecurityToken = new JwtSecurityToken( issuer: _configuration.Issuer, audience: _configuration.Audience, claims: claims, notBefore: now, expires: now.Add(expiration ?? _configuration.Expiration), signingCredentials: _configuration.SigningCredentials ); return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); } private static List CreateJwtClaims(ClaimsIdentity identity) { var claims = identity.Claims.ToList(); var nameIdClaim = claims.First(c => c.Type == ClaimTypes.NameIdentifier); // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims. claims.AddRange(new[] { new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) }); return claims; } private string GetEncryptedAccessToken(string accessToken) { return SimpleStringCipher.Instance.Encrypt(accessToken); } } }