Base solution for your next web application
Open Closed

Auditing logins #5342


User avatar
0
BobIngham created

aspnet-core, angular 5.4.1 I am getting no entries from method AppAuthenticate in AppTokenAuthController. I have made the method virtual as per the documentation and added the [Audited] decorator but the login is not registered with the AuditLogStore.

How do I audit logins with the Zero framework?


16 Answer(s)
  • User Avatar
    0
    ryancyq created
    Support Team

    Can you show us the code of how you implement AppAuthenticate and AppTokenAuthController

  • User Avatar
    0
    aaron created
    Support Team

    Did you set IsEnabledForAnonymousUsers = true?

    but the login is not registered with the AuditLogStore

    Do you mean that you do not see it in AbpAuditLogs table?

  • User Avatar
    0
    BobIngham created

    Hi Aaron, thanks for getting back. I have set the configuration for Auditing:

    Configuration.Auditing.IsEnabledForAnonymousUsers = true;
    

    . There is nothing in the AbpAuditLogs table and AuditingStore.SaveAsync (...) is not fired when debugging logins.

  • User Avatar
    0
    BobIngham created

    @ryancyq that's almost 600 lines of code, are you sure you want me to post it? There are some modifications made to allow logins from an Ionic app otherwise it's fairly standard. If you want me to share I will gladly do so.

  • User Avatar
    0
    aaron created
    Support Team

    There is nothing in the AbpAuditLogs table and AuditingStore.SaveAsync (...) is not fired when debugging logins.

    You mean you replaced IAuditingStore? If so, show your implementation. AuditingStore.SaveAsync is not fired inside a method, but after it returns.

    Since you are referring to your own AppAuthenticate method, avoid the term "login" as there's a method by that name. Why aren't you using the original Authenticate method in the original TokenAuthController?

  • User Avatar
    0
    BobIngham created

    @Aaron, In my implementation I have replaced both IAuditingStore and IEntityHistoryStore:

    Configuration.ReplaceService<IEntityHistoryStore, MongodbEntityHistoryStore>(DependencyLifeStyle.Transient);
    Configuration.ReplaceService<IAuditingStore, MongodbAuditingStore>(DependencyLifeStyle.Transient);
    

    and added a new audit log app service, MongodbAuditLogAppService.cs. The three tables in Azure's SQL Server (dbo.AbpEntityPropertyChanges, dbo.AbpAuditLogs and dbo.AbpEntityChanges) meant I would have to upgrade my SQL Server just to store this immutable data. Because my system is built on data protection compliancy this history is a key part of the project. I have made the decision to store immutable history data in Mongodb. Here is my MongodbAuditingStore:

    using Abp.Auditing;
    using Abp.Dependency;
    using Abp.Domain.Repositories;
    using Abp.Runtime.Session;
    using AutoMapper;
    using MongoDB.Bson;
    using MongoDB.Driver;
    using Nuagecare.App.Auditing;
    using Nuagecare.Mongodb;
    using Nuagecare.MultiTenancy;
    using System;
    using System.Threading.Tasks;
    
    namespace Nuagecare.App.Mongodb
    {
        public class MongodbAuditingStore : IAuditingStore, ITransientDependency
        {
            private readonly IRepository<Tenant> _tenantRepository;
            public IAbpSession AbpSession { get; set; }
    
            /// <summary>
            /// Creates  a new <see cref="AuditingStore"/>.
            /// </summary>
            public MongodbAuditingStore(
                IRepository<Tenant> tenantRepository)
            {
                AbpSession = NullAbpSession.Instance;
                _tenantRepository = tenantRepository;
            }
    
            public async Task SaveAsync(AuditInfo auditInfo)
            {
                var mongoAuditInfo = Mapper.Map<MongoAuditInfo>(auditInfo);
                var dataBaseName = "projectname";
    
                var mongoClient = new MongodbConfig().Initialize();
                if (AbpSession.TenantId != null)
                {
                    dataBaseName = GetTenancyNameFromTenantId(Convert.ToInt32(AbpSession.TenantId));
                }
                IMongoDatabase db = mongoClient.GetDatabase(dataBaseName);
                var collection = db.GetCollection<BsonDocument>("auditLog");
                var document = mongoAuditInfo.ToBsonDocument();
                await collection.InsertOneAsync(document);
            }
    
            private string GetTenancyNameFromTenantId(int tenantId)
            {
                var tenant = _tenantRepository.FirstOrDefault(tenantId);
                if (tenant == null)
                {
                    return "projectname";
                }
                return tenant.TenancyName;
            }
        }
    }
    

    Everything in the modified system is working perfectly, I am storing entity history and audit log info in Mongodb and MongodbAuditLogAppService.cs returns the correct data to the angular projects.

    On the subject of my own AppAuthenticate method in my own controller, AppTokenAuthController this is implemented to support tokens for Ionic apps and requires some bespoke code. Here is my AppAuthenticate method:

    [HttpPost]
    [DontWrapResult(WrapOnError = true)]
    [Audited]
    public virtual async Task<AppAuthenticateResultModel> AppAuthenticate([FromBody] AppAuthenticateModel model)
    {
        // Nuage stuff - null tenant value
        if (model.TenantId == 0)
        {
            throw new UserFriendlyException(L("PleaseRegisterThisDeviceBeforeSigningIn."));
        }
    
        //if the user exists in the data provider but not in Zero we need to add a new user....
        using (_unitOfWorkManager.Current.SetTenantId(model.TenantId))
        {
            //see https://forum.aspnetboilerplate.com/viewtopic.php?f=5&t=10044&p=23035&hilit=set+abpsession&sid=99db30a39413f544f1b2bfeb49ff4d18#p23035
            AbpSession.Use(model.TenantId, null);
            var tenantDataProvider = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProvider);
    
            var DataProviderApiUrl = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProviderApiUrl);
            if (String.IsNullOrEmpty(DataProviderApiUrl))
            {
                throw new UserFriendlyException(L("InvalidDataProviderApiUrl"));
            }
    
            if (tenantDataProvider != "Nuage")
            {
                var zeroUser = await _userManager.FindByNameOrEmailAsync(model.UserNameOrEmailAddress);
                if (zeroUser == null)
                {
                    await _userPolicy.CheckMaxUserCountAsync(model.TenantId);
                    var DataProviderUser = await _dataProviderAPIProvider.DataProviderLoginAndReturnUser(model.UserNameOrEmailAddress, model.Password);
                    if (DataProviderUser != null)
                    {
                        await CreateDataProviderUser(model, DataProviderUser);
                    }
                    else
                    {
                        throw new UserFriendlyException(L("InvalidUserNameOrPassword"));
                    }
                }
            }
        }
    
        var loginResult = await GetLoginResultAsync(
            model.UserNameOrEmailAddress,
            model.Password,
            await GetTenancyNameFromTenantId(model.TenantId)
        );
        var returnUrl = model.ReturnUrl;
    
        if (model.SingleSignIn.HasValue && model.SingleSignIn.Value && loginResult.Result == AbpLoginResultType.Success)
        {
            loginResult.User.SetSignInToken();
            returnUrl = AddSingleSignInParametersToReturnUrl(model.ReturnUrl, loginResult.User.SignInToken, loginResult.User.Id, loginResult.User.TenantId);
        }
    
        //Password reset
        if (loginResult.User.ShouldChangePasswordOnNextLogin)
        {
            loginResult.User.SetNewPasswordResetCode();
            return new AppAuthenticateResultModel
            {
                ShouldResetPassword = true,
                PasswordResetCode = loginResult.User.PasswordResetCode,
                UserId = loginResult.User.Id,
                ReturnUrl = returnUrl
            };
        }
    
        //Two factor auth
        AuthenticateModel zeroAuthenticateModel = new AuthenticateModel
        {
            UserNameOrEmailAddress = model.UserNameOrEmailAddress,
            Password = model.Password,
            TwoFactorVerificationCode = model.TwoFactorVerificationCode,
            RememberClient = model.RememberClient,
            TwoFactorRememberClientToken = model.TwoFactorRememberClientToken,
            SingleSignIn = model.SingleSignIn,
            ReturnUrl = model.ReturnUrl
        };
    
        await _userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);
        string twoFactorRememberClientToken = null;
        if (await IsTwoFactorAuthRequiredAsync(loginResult, zeroAuthenticateModel))
        {
            if (model.TwoFactorVerificationCode.IsNullOrEmpty())
            {
                //Add a cache item which will be checked in SendTwoFactorAuthCode to prevent sending unwanted two factor code to users.
                _cacheManager
                    .GetTwoFactorCodeCache()
                    .Set(
                        loginResult.User.ToUserIdentifier().ToString(),
                        new TwoFactorCodeCacheItem()
                    );
    
                return new AppAuthenticateResultModel
                {
                    RequiresTwoFactorVerification = true,
                    UserId = loginResult.User.Id,
                    TwoFactorAuthProviders = await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User),
                    ReturnUrl = returnUrl
                };
            }
    
            twoFactorRememberClientToken = await TwoFactorAuthenticateAsync(loginResult.User, zeroAuthenticateModel);
        }
    
        //Login!
        var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
    
        // Nuage stuff - LoginAudit
        var audit = new NcDeviceAndUserLoginAudit
        {
            DeviceId = model.DeviceId,
            UserId = loginResult.User.Id,
            LoginDatetime = Clock.Now
        };
    
        //insert LoginAudit - TODO - should be Mongodb
        await _deviceAndUserLoginAuditRepository.InsertAsync(audit);
    
        return new AppAuthenticateResultModel
        {
            AccessToken = accessToken,
            EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
            ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
            TwoFactorRememberClientToken = twoFactorRememberClientToken,
            UserId = loginResult.User.Id,
            ReturnUrl = returnUrl,
            UserDisplayName = loginResult.User.FullName,
            TenantName = await GetTenancyNameFromTenantId(model.TenantId),
            NuageSettingsDto = await _tenantSettingsAppService.GetNuageSettingsAsync()
        };
    }
    

    I apologise in advance for my code, I'm not up there with the best of them. However, I think we're getting too deep into details. The fact is that the AppAuthenticate method in my own controller, AppTokenAuthController does not place an entry into the audit logs. But, if I login using the angular front end the standard Authenticate method in the Zero controller, TokenAuthController it also does not place an entry into the audit logs. So, the question remains,

    How do store logins in the audit logs?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Is your AppTokenAuthController derives from Controller class or [YourProjectName]ControllerBase ?

  • User Avatar
    0
    BobIngham created

    Hi @ismcagdas , thanks for getting back. My AppTokenAuthController inherits from [YourProjectName]ControllerBase and it used only for logging in through an Ionic app :

    namespace Nuagecare.Web.Controllers
    {
        [Route("api/[controller]/[action]")]
        public class AppTokenAuthController : NuagecareControllerBase
        {
    

    But I think we are missing the point in concentrating on my code. The fact is that when I login to the system using the standard angular front-end the Zero standard TokenAuthController is used. No audit-log entry is made from the TokenAuthController.Authenticate method. I would like to see logins in my audit log so I can implement an audit log table per user. It would be nice to see an initial login entry followed by the standard audit-log entries.

    Is it possible to have logins audited and, if so, how?

  • User Avatar
    0
    aaron created
    Support Team

    TokenAuthController.Authenticate is already audited. Try it on a fresh download.

  • User Avatar
    0
    BobIngham created

    @Aaron, I stand corrected. TokenAuthController.Authenticate is already audited. I am unable to upload my AppTokenAuthController so here it is in it's entirety. Interestingly the method ExpirationHeartbeat is fully audited (and has to be turned off) but the method AppAuthenticate is not audited. Could you take a look and suggest anything?

    using Abp;
    using Abp.AspNetZeroCore.Web.Authentication.External;
    using Abp.Auditing;
    using Abp.Authorization;
    using Abp.Authorization.Users;
    using Abp.Configuration;
    using Abp.Domain.Repositories;
    using Abp.Domain.Uow;
    using Abp.Extensions;
    using Abp.MultiTenancy;
    using Abp.Net.Mail;
    using Abp.Runtime.Caching;
    using Abp.Runtime.Security;
    using Abp.Timing;
    using Abp.UI;
    using Abp.Web.Models;
    using Abp.Zero.Configuration;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Options;
    using Microsoft.IdentityModel.Tokens;
    using Nuagecare.App.Services;
    using Nuagecare.Authentication.TwoFactor.Google;
    using Nuagecare.Authorization;
    using Nuagecare.Authorization.Devices;
    using Nuagecare.Authorization.Impersonation;
    using Nuagecare.Authorization.Roles;
    using Nuagecare.Authorization.Users;
    using Nuagecare.Configuration;
    using Nuagecare.Configuration.Tenants;
    using Nuagecare.DataProvider.Dtos;
    using Nuagecare.Identity;
    using Nuagecare.MultiTenancy;
    using Nuagecare.Notifications;
    using Nuagecare.Web.Authentication.JwtBearer;
    using Nuagecare.Web.Authentication.TwoFactor;
    using Nuagecare.Web.Models.TokenAuth;
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Nuagecare.Web.Controllers
    {
        [Route("api/[controller]/[action]")]
        public class AppTokenAuthController : NuagecareControllerBase
        {
            private const string UserIdentifierClaimType = "http://aspnetzero.com/claims/useridentifier";
            private readonly LogInManager _logInManager;
            private readonly ITenantCache _tenantCache;
            private readonly AbpLoginResultTypeHelper _abpLoginResultTypeHelper;
            private readonly TokenAuthConfiguration _configuration;
            private readonly UserManager _userManager;
            private readonly ICacheManager _cacheManager;
            private readonly IOptions<JwtBearerOptions> _jwtOptions;
            private readonly IExternalAuthConfiguration _externalAuthConfiguration;
            private readonly IExternalAuthManager _externalAuthManager;
            private readonly UserRegistrationManager _userRegistrationManager;
            private readonly IImpersonationManager _impersonationManager;
            private readonly IUserLinkManager _userLinkManager;
            private readonly IAppNotifier _appNotifier;
            private readonly ISmsSender _smsSender;
            private readonly IEmailSender _emailSender;
            private readonly IdentityOptions _identityOptions;
            private readonly GoogleAuthenticatorProvider _googleAuthenticatorProvider;
    
            //Nuage settings
            private readonly TenantManager _tenantManager;
            private readonly IUnitOfWorkManager _unitOfWorkManager;
            private readonly DataProviderAPIProvider _dataProviderAPIProvider;
            private readonly IUserPolicy _userPolicy;
            private readonly IPasswordHasher<User> _passwordHasher;
            private readonly RoleManager _roleManager;
            private readonly TenantSettingsAppService _tenantSettingsAppService;
            private readonly IRepository<NcDeviceAndUserLoginAudit> _deviceAndUserLoginAuditRepository;
    
            public AppTokenAuthController(
                LogInManager logInManager,
                ITenantCache tenantCache,
                AbpLoginResultTypeHelper abpLoginResultTypeHelper,
                TokenAuthConfiguration configuration,
                UserManager userManager,
                ICacheManager cacheManager,
                IOptions<JwtBearerOptions> jwtOptions,
                IExternalAuthConfiguration externalAuthConfiguration,
                IExternalAuthManager externalAuthManager,
                UserRegistrationManager userRegistrationManager,
                IImpersonationManager impersonationManager,
                IUserLinkManager userLinkManager,
                IAppNotifier appNotifier,
                ISmsSender smsSender,
                IEmailSender emailSender,
                IOptions<IdentityOptions> identityOptions,
                GoogleAuthenticatorProvider googleAuthenticatorProvider,
                TenantManager tenantManager,
                IUnitOfWorkManager unitOfWorkManager,
                DataProviderAPIProvider dataProviderAPIProvider,
                IUserPolicy userPolicy,
                IPasswordHasher<User> passwordHasher,
                RoleManager roleManager,
                TenantSettingsAppService tenantSettingsAppService,
                IRepository<NcDeviceAndUserLoginAudit> deviceAndUserLoginAuditRepository)
            {
                _logInManager = logInManager;
                _tenantCache = tenantCache;
                _abpLoginResultTypeHelper = abpLoginResultTypeHelper;
                _configuration = configuration;
                _userManager = userManager;
                _cacheManager = cacheManager;
                _jwtOptions = jwtOptions;
                _externalAuthConfiguration = externalAuthConfiguration;
                _externalAuthManager = externalAuthManager;
                _userRegistrationManager = userRegistrationManager;
                _impersonationManager = impersonationManager;
                _userLinkManager = userLinkManager;
                _appNotifier = appNotifier;
                _smsSender = smsSender;
                _emailSender = emailSender;
                _googleAuthenticatorProvider = googleAuthenticatorProvider;
                _identityOptions = identityOptions.Value;
                _tenantManager = tenantManager;
                _unitOfWorkManager = unitOfWorkManager;
                _dataProviderAPIProvider = dataProviderAPIProvider;
                _userPolicy = userPolicy;
                _passwordHasher = passwordHasher;
                _roleManager = roleManager;
                _tenantSettingsAppService = tenantSettingsAppService;
                _deviceAndUserLoginAuditRepository = deviceAndUserLoginAuditRepository;
            }
    
            [HttpPost]
            [AbpAuthorize]
            [DisableAuditing]
            public string ExpirationHeartbeat()
            {
                return "Ok";
            }
    
            [HttpPost]
            [DontWrapResult(WrapOnError = true)]
            [Audited]
            public async Task<AppAuthenticateResultModel> AppAuthenticate([FromBody] AppAuthenticateModel model)
            {
                // Nuage stuff - null tenant value
                if (model.TenantId == 0)
                {
                    throw new UserFriendlyException(L("PleaseRegisterThisDeviceBeforeSigningIn."));
                }
    
                //if the user exists in the data provider but not in Zero we need to add a new user....
                using (_unitOfWorkManager.Current.SetTenantId(model.TenantId))
                {
                    //see https://forum.aspnetboilerplate.com/viewtopic.php?f=5&t=10044&p=23035&hilit=set+abpsession&sid=99db30a39413f544f1b2bfeb49ff4d18#p23035
                    AbpSession.Use(model.TenantId, null);
                    var tenantDataProvider = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProvider);
    
                    var DataProviderApiUrl = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProviderApiUrl);
                    if (String.IsNullOrEmpty(DataProviderApiUrl))
                    {
                        throw new UserFriendlyException(L("InvalidDataProviderApiUrl"));
                    }
    
                    if (tenantDataProvider != "Nuage")
                    {
                        var zeroUser = await _userManager.FindByNameOrEmailAsync(model.UserNameOrEmailAddress);
                        if (zeroUser == null)
                        {
                            await _userPolicy.CheckMaxUserCountAsync(model.TenantId);
                            var DataProviderUser = await _dataProviderAPIProvider.DataProviderLoginAndReturnUser(model.UserNameOrEmailAddress, model.Password);
                            if (DataProviderUser != null)
                            {
                                await CreateDataProviderUser(model, DataProviderUser);
                            }
                            else
                            {
                                throw new UserFriendlyException(L("InvalidUserNameOrPassword"));
                            }
                        }
                    }
                }
    
                var loginResult = await GetLoginResultAsync(
                  model.UserNameOrEmailAddress,
                  model.Password,
                  await GetTenancyNameFromTenantId(model.TenantId)
                );
                var returnUrl = model.ReturnUrl;
    
                if (model.SingleSignIn.HasValue && model.SingleSignIn.Value && loginResult.Result == AbpLoginResultType.Success)
                {
                    loginResult.User.SetSignInToken();
                    returnUrl = AddSingleSignInParametersToReturnUrl(model.ReturnUrl, loginResult.User.SignInToken, loginResult.User.Id, loginResult.User.TenantId);
                }
    
                //Password reset
                if (loginResult.User.ShouldChangePasswordOnNextLogin)
                {
                    loginResult.User.SetNewPasswordResetCode();
                    return new AppAuthenticateResultModel
                    {
                        ShouldResetPassword = true,
                        PasswordResetCode = loginResult.User.PasswordResetCode,
                        UserId = loginResult.User.Id,
                        ReturnUrl = returnUrl
                    };
                }
    
                //Two factor auth
                AuthenticateModel zeroAuthenticateModel = new AuthenticateModel
                {
                    UserNameOrEmailAddress = model.UserNameOrEmailAddress,
                    Password = model.Password,
                    TwoFactorVerificationCode = model.TwoFactorVerificationCode,
                    RememberClient = model.RememberClient,
                    TwoFactorRememberClientToken = model.TwoFactorRememberClientToken,
                    SingleSignIn = model.SingleSignIn,
                    ReturnUrl = model.ReturnUrl
                };
    
                await _userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);
                string twoFactorRememberClientToken = null;
                if (await IsTwoFactorAuthRequiredAsync(loginResult, zeroAuthenticateModel))
                {
                    if (model.TwoFactorVerificationCode.IsNullOrEmpty())
                    {
                        //Add a cache item which will be checked in SendTwoFactorAuthCode to prevent sending unwanted two factor code to users.
                        _cacheManager
                            .GetTwoFactorCodeCache()
                            .Set(
                                loginResult.User.ToUserIdentifier().ToString(),
                                new TwoFactorCodeCacheItem()
                            );
    
                        return new AppAuthenticateResultModel
                        {
                            RequiresTwoFactorVerification = true,
                            UserId = loginResult.User.Id,
                            TwoFactorAuthProviders = await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User),
                            ReturnUrl = returnUrl
                        };
                    }
    
                    twoFactorRememberClientToken = await TwoFactorAuthenticateAsync(loginResult.User, zeroAuthenticateModel);
                }
    
                //Login!
                var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
    
                // Nuage stuff - LoginAudit
                var audit = new NcDeviceAndUserLoginAudit
                {
                    DeviceId = model.DeviceId,
                    UserId = loginResult.User.Id,
                    LoginDatetime = Clock.Now
                };
    
                //insert LoginAudit - TODO - should be Mongodb
                await _deviceAndUserLoginAuditRepository.InsertAsync(audit);
    
                return new AppAuthenticateResultModel
                {
                    AccessToken = accessToken,
                    EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
                    ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
                    TwoFactorRememberClientToken = twoFactorRememberClientToken,
                    UserId = loginResult.User.Id,
                    ReturnUrl = returnUrl,
                    UserDisplayName = loginResult.User.FullName,
                    TenantName = await GetTenancyNameFromTenantId(model.TenantId),
                    NuageSettingsDto = await _tenantSettingsAppService.GetNuageSettingsAsync()
                };
            }
    
            [HttpPost]
            public async Task SendTwoFactorAuthCode([FromBody] SendTwoFactorAuthCodeModel model)
            {
                var cacheKey = new UserIdentifier(AbpSession.TenantId, model.UserId).ToString();
    
                var cacheItem = await _cacheManager
                    .GetTwoFactorCodeCache()
                    .GetOrDefaultAsync(cacheKey);
    
                if (cacheItem == null)
                {
                    //There should be a cache item added in Authenticate method! This check is needed to prevent sending unwanted two factor code to users.
                    throw new UserFriendlyException(L("SendSecurityCodeErrorMessage"));
                }
    
                var user = await _userManager.FindByIdAsync(model.UserId.ToString());
    
                if (model.Provider != GoogleAuthenticatorProvider.Name)
                {
                    cacheItem.Code = await _userManager.GenerateTwoFactorTokenAsync(user, model.Provider);
                    var message = L("EmailSecurityCodeBody", cacheItem.Code);
    
                    if (model.Provider == "Email")
                    {
                        await _emailSender.SendAsync(await _userManager.GetEmailAsync(user), L("EmailSecurityCodeSubject"),
                            message);
                    }
                    else if (model.Provider == "Phone")
                    {
                        await _smsSender.SendAsync(await _userManager.GetPhoneNumberAsync(user), message);
                    }
                }
    
                _cacheManager.GetTwoFactorCodeCache().Set(
                        cacheKey,
                        cacheItem
                    );
                _cacheManager.GetCache("ProviderCache").Set(
                    "Provider",
                    model.Provider
                );
            }
    
            [HttpPost]
            public async Task<ImpersonatedAuthenticateResultModel> ImpersonatedAuthenticate(string impersonationToken)
            {
                var result = await _impersonationManager.GetImpersonatedUserAndIdentity(impersonationToken);
                var accessToken = CreateAccessToken(CreateJwtClaims(result.Identity));
    
                return new ImpersonatedAuthenticateResultModel
                {
                    AccessToken = accessToken,
                    EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
                    ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
                };
            }
    
            [HttpPost]
            public async Task<SwitchedAccountAuthenticateResultModel> LinkedAccountAuthenticate(string switchAccountToken)
            {
                var result = await _userLinkManager.GetSwitchedUserAndIdentity(switchAccountToken);
                var accessToken = CreateAccessToken(CreateJwtClaims(result.Identity));
    
                return new SwitchedAccountAuthenticateResultModel
                {
                    AccessToken = accessToken,
                    EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
                    ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
                };
            }
    
            [HttpGet]
            public List<ExternalLoginProviderInfoModel> GetExternalAuthenticationProviders()
            {
                return ObjectMapper.Map<List<ExternalLoginProviderInfoModel>>(_externalAuthConfiguration.Providers);
            }
    
            private async Task<bool> IsTwoFactorAuthRequiredAsync(AbpLoginResult<Tenant, User> loginResult, AuthenticateModel authenticateModel)
            {
                if (!await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEnabled))
                {
                    return false;
                }
    
                if (!loginResult.User.IsTwoFactorEnabled)
                {
                    return false;
                }
    
                if ((await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User)).Count <= 0)
                {
                    return false;
                }
    
                if (await TwoFactorClientRememberedAsync(loginResult.User.ToUserIdentifier(), authenticateModel))
                {
                    return false;
                }
    
                return true;
            }
    
            private async Task<bool> TwoFactorClientRememberedAsync(UserIdentifier userIdentifier, AuthenticateModel authenticateModel)
            {
                if (!await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsRememberBrowserEnabled))
                {
                    return false;
                }
    
                if (string.IsNullOrWhiteSpace(authenticateModel.TwoFactorRememberClientToken))
                {
                    return false;
                }
    
                try
                {
                    var validationParameters = new TokenValidationParameters
                    {
                        ValidAudience = _configuration.Audience,
                        ValidIssuer = _configuration.Issuer,
                        IssuerSigningKey = _configuration.SecurityKey
                    };
    
                    foreach (var validator in _jwtOptions.Value.SecurityTokenValidators)
                    {
                        if (validator.CanReadToken(authenticateModel.TwoFactorRememberClientToken))
                        {
                            try
                            {
                                SecurityToken validatedToken;
                                var principal = validator.ValidateToken(authenticateModel.TwoFactorRememberClientToken, validationParameters, out validatedToken);
                                var useridentifierClaim = principal.FindFirst(c => c.Type == UserIdentifierClaimType);
                                if (useridentifierClaim == null)
                                {
                                    return false;
                                }
    
                                return useridentifierClaim.Value == userIdentifier.ToString();
                            }
                            catch (Exception ex)
                            {
                                Logger.Debug(ex.ToString(), ex);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.Debug(ex.ToString(), ex);
                }
    
                return false;
            }
    
            /* Checkes two factor code and returns a token to remember the client (browser) if needed */
            private async Task<string> TwoFactorAuthenticateAsync(User user, AuthenticateModel authenticateModel)
            {
                var twoFactorCodeCache = _cacheManager.GetTwoFactorCodeCache();
                var userIdentifier = user.ToUserIdentifier().ToString();
                var cachedCode = await twoFactorCodeCache.GetOrDefaultAsync(userIdentifier);
                var provider = _cacheManager.GetCache("ProviderCache").Get("Provider", cache => cache).ToString();
    
                if (provider == GoogleAuthenticatorProvider.Name)
                {
                    if (!await _googleAuthenticatorProvider.ValidateAsync("TwoFactor", authenticateModel.TwoFactorVerificationCode, _userManager, user))
                    {
                        throw new UserFriendlyException(L("InvalidSecurityCode"));
                    }
                }
                else if (cachedCode?.Code == null || cachedCode.Code != authenticateModel.TwoFactorVerificationCode)
                {
                    throw new UserFriendlyException(L("InvalidSecurityCode"));
                }
    
                //Delete from the cache since it was a single usage code
                await twoFactorCodeCache.RemoveAsync(userIdentifier);
    
                if (authenticateModel.RememberClient)
                {
                    if (await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsRememberBrowserEnabled))
                    {
                        return CreateAccessToken(new[]
                            {
                                new Claim(UserIdentifierClaimType, user.ToUserIdentifier().ToString())
                            },
                            TimeSpan.FromDays(365)
                        );
                    }
                }
    
                return null;
            }
    
            private async Task<string> GetTenancyNameFromTenantId(int tenantId)
            {
                var tenant = await _tenantManager.FindByIdAsync(tenantId);
                if (tenant == null)
                {
                    return null;
                }
                return tenant.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 string GetEncrpytedAccessToken(string accessToken)
            {
                return SimpleStringCipher.Instance.Encrypt(accessToken, AppConsts.DefaultPassPhrase);
            }
    
            private List<Claim> CreateJwtClaims(ClaimsIdentity identity)
            {
                var claims = identity.Claims.ToList();
                var nameIdClaim = claims.First(c => c.Type == _identityOptions.ClaimsIdentity.UserIdClaimType);
    
                if (_identityOptions.ClaimsIdentity.UserIdClaimType != JwtRegisteredClaimNames.Sub)
                {
                    claims.Add(new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value));
                }
    
                claims.AddRange(new[]
                {
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                    new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
                });
    
                return claims;
            }
    
            private string AddSingleSignInParametersToReturnUrl(string returnUrl, string signInToken, long userId, int? tenantId)
            {
                returnUrl += (returnUrl.Contains("?") ? "&" : "?") +
                             "accessToken=" + signInToken +
                             "&userId=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(userId.ToString()));
                if (tenantId.HasValue)
                {
                    returnUrl += "&tenantId=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(tenantId.Value.ToString()));
                }
    
                return returnUrl;
            }
    
            protected virtual async Task<User> CreateDataProviderUser(AppAuthenticateModel model, DataProviderUserDto DataProviderUser)
            {
                User user = new User();
                    using (var unitOfWork = _unitOfWorkManager.Begin())
                    {
                        user.TenantId = model.TenantId;
                        user.UserName = model.UserNameOrEmailAddress;
                        user.Password = model.Password;
                        user.IsEmailConfirmed = false; //isEmailConfirmed,
                        user.IsActive = true;
                        user.Name = DataProviderUser.DisplayName;
                        user.Name = DataProviderUser.Firstname;
                        user.Surname = DataProviderUser.Lastname;
                        user.Roles = new List<UserRole>();
    
                        //we must have an email unique to each tenant
                        if (!String.IsNullOrEmpty(DataProviderUser.Email))
                        {
                            user.EmailAddress = DataProviderUser.Email;
                        }
                        else
                        {
                            user.EmailAddress = user.UserName.ToLowerInvariant() + "@noemail.io";
                        }
                        user.SetNormalizedNames();
    
                        user.Password = _passwordHasher.HashPassword(user, model.Password);
    
                        foreach (var defaultRole in await _roleManager.Roles.Where(r => r.IsDefault).ToListAsync())
                        {
                            user.Roles.Add(new UserRole(model.TenantId, user.Id, defaultRole.Id));
                        }
                        CheckErrors(await _userManager.CreateAsync(user));
    
                        AbpSession.Use(model.TenantId, user.Id);
                        await SettingManager.ChangeSettingForUserAsync(new UserIdentifier(model.TenantId, user.Id), NuageSettings.NuageUserManagement.DataProviderUserId, DataProviderUser.UserId);
                        await unitOfWork.CompleteAsync();
                    }
                return user;
            }
        }
    }
    

    Cheers, Bob

  • User Avatar
    0
    ryancyq created
    Support Team

    Hi @bobingham,

    Can you try to remove [AbpAuthorize] and [DisableAuditing] for ExpirationHeartbeat() and check if it still being audited

  • User Avatar
    0
    BobIngham created

    Hi @ryancyq, yes ExpirationHeartbeat() is still audited if comment out [AbpAuthorize] and [DisableAuditing] .

  • User Avatar
    0
    BobIngham created

    All, Is there any chance that the TokenAuthController.Authenticate method does not write an audit log under certain circumstances? I have found one entry in the audit log for my AppTokenAuthController.AppAuthenticate method but then when I sign out on the app (the token is removed from app storage) and sign back in no entry is persisted. Stranger and stranger.

  • User Avatar
    0
    aaron created
    Support Team

    Check if there's any error in Logs.txt.

  • User Avatar
    0
    BobIngham created

    No, no errors. The method works as expected.

  • User Avatar
    0
    BobIngham created

    I guess this will have to wait until I have more time to investigate further.