153 lines
5.7 KiB
C#
153 lines
5.7 KiB
C#
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<AuthenticateResultModel> 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
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Authenticate with external OIDC/OAuth2 provider (Authentik, Keycloak, etc.)
|
|
/// Passthrough approach: validates external token and returns it as-is
|
|
/// </summary>
|
|
[HttpPost]
|
|
[AbpAllowAnonymous]
|
|
public async Task<AuthenticateResultModel> 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<AbpLoginResult<Tenant, User>> 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<Claim> 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<Claim> 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|