Base solution for your next web application

Activities of "SBJ"

Prerequisites

Please answer the following questions before submitting an issue.

  • What is your product version? 8.7.0
  • What is your product type (Angular or MVC)? MVC
  • What is product framework type (.net framework or .net core)? .NET Core

<br> Hello,

We are currently working on ahybrid with asp.net core and Vue.js. Vue as our running frontend. Because we call Services and Controllers in the frontend we want to authenticate them. So we have been working on getting the authentication to work and added the TokenAuthController to the frontend as a seperate Service. This service has no errors and we did services.AddScoped in the startup.cs but when we try to run it gives back the following error:

System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: RMS.Web.Website.Philips.Services.IAuthenticationService Lifetime: Transient ImplementationType: RMS.Web.Website.Philips.AuthenticationService': Unable to resolve service for type 'RMS.Authorization.LogInManager' while attempting to activate 'RMS.Web.Website.Philips.AuthenticationService'.)'

How can we authenticate the frontend so we can safely call our Services/Controller?

AuthenticationService.cs:

    public async Task<AuthenticateResultModel> Authenticate(AuthenticateModel model)
    {
        /*if (UseCaptchaOnLogin())
        {
            await ValidateReCaptcha(model.CaptchaResponse);
        }*/

        var loginResult = await GetLoginResultAsync(
            model.UserNameOrEmailAddress,
            model.Password,
            GetTenancyNameOrNull()
        );

        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 AuthenticateResultModel
            {
                ShouldResetPassword = true,
                PasswordResetCode = loginResult.User.PasswordResetCode,
                UserId = loginResult.User.Id,
                ReturnUrl = returnUrl
            };
        }

        //Two factor auth
        await _userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);

        string twoFactorRememberClientToken = null;
        if (await IsTwoFactorAuthRequiredAsync(loginResult, model))
        {
            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 AuthenticateResultModel
                {
                    RequiresTwoFactorVerification = true,
                    UserId = loginResult.User.Id,
                    TwoFactorAuthProviders = await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User),
                    ReturnUrl = returnUrl
                };
            }

            twoFactorRememberClientToken = await TwoFactorAuthenticateAsync(loginResult.User, model);
        }

        // One Concurrent Login 
        if (AllowOneConcurrentLoginPerUser())
        {
            await _userManager.UpdateSecurityStampAsync(loginResult.User);
            await _securityStampHandler.SetSecurityStampCacheItem(loginResult.User.TenantId, loginResult.User.Id, loginResult.User.SecurityStamp);
            loginResult.Identity.ReplaceClaim(new Claim(AppConsts.SecurityStampKey, loginResult.User.SecurityStamp));
        }

        var accessToken = CreateAccessToken(await CreateJwtClaims(loginResult.Identity, loginResult.User));
        var refreshToken = CreateRefreshToken(await CreateJwtClaims(loginResult.Identity, loginResult.User, tokenType: TokenType.RefreshToken));

        return new AuthenticateResultModel
        {
            AccessToken = accessToken,
            ExpireInSeconds = (int)_configuration.AccessTokenExpiration.TotalSeconds,
            RefreshToken = refreshToken,
            RefreshTokenExpireInSeconds = (int)_configuration.RefreshTokenExpiration.TotalSeconds,
            EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
            TwoFactorRememberClientToken = twoFactorRememberClientToken,
            UserId = loginResult.User.Id,
            ReturnUrl = returnUrl
        };
    }

    private bool UseCaptchaOnLogin()
    {
        return _settingManager.GetSettingValue<bool>(AppSettings.UserManagement.UseCaptchaOnLogin);
    }

    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 GetTenancyNameOrNull()
    {
        /*            if (!AbpSession.TenantId.HasValue)
                    {
                        return null;
                    }*/

        /*return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName;*/

        return _appConfiguration["Tenant:Name"];
    }

    private static 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;
    }

    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<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"));*/
                return null;
            }
        }
        else if (cachedCode?.Code == null || cachedCode.Code != authenticateModel.TwoFactorVerificationCode)
        {
            //throw new UserFriendlyException(L("InvalidSecurityCode"));
            return null;
        }

        //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 bool AllowOneConcurrentLoginPerUser()
    {
        return _settingManager.GetSettingValue<bool>(AppSettings.UserManagement.AllowOneConcurrentLoginPerUser);
    }

    private string CreateAccessToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
    {
        return CreateToken(claims, expiration ?? _configuration.AccessTokenExpiration);
    }

    private string CreateRefreshToken(IEnumerable<Claim> claims)
    {
        return CreateToken(claims, AppConsts.RefreshTokenExpiration);
    }

    private async Task<IEnumerable<Claim>> CreateJwtClaims(ClaimsIdentity identity, User user, TimeSpan? expiration = null, TokenType tokenType = TokenType.AccessToken)
    {
        var tokenValidityKey = Guid.NewGuid().ToString();
        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),
            new Claim(AppConsts.TokenValidityKey, tokenValidityKey),
            new Claim(AppConsts.UserIdentifier, user.ToUserIdentifier().ToUserIdentifierString()),
            new Claim(AppConsts.TokenType, tokenType.To<int>().ToString())
         });

        if (!expiration.HasValue)
        {
            expiration = tokenType == TokenType.AccessToken
                ? _configuration.AccessTokenExpiration
                : _configuration.RefreshTokenExpiration;
        }

        _cacheManager
            .GetCache(AppConsts.TokenValidityKey)
            .Set(tokenValidityKey, "", absoluteExpireTime: expiration);

        await _userManager.AddTokenValidityKeyAsync(
            user,
            tokenValidityKey,
            DateTime.UtcNow.Add(expiration.Value)
        );

        return claims;
    }

    private static string GetEncryptedAccessToken(string accessToken)
    {
        return SimpleStringCipher.Instance.Encrypt(accessToken, AppConsts.DefaultPassPhrase);
    }

    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
                    {
                        var principal = validator.ValidateToken(authenticateModel.TwoFactorRememberClientToken, validationParameters, out _);
                        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;
    }

    private string CreateToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
    {
        var now = DateTime.UtcNow;

        var jwtSecurityToken = new JwtSecurityToken(
            issuer: _configuration.Issuer,
            audience: _configuration.Audience,
            claims: claims,
            notBefore: now,
            signingCredentials: _configuration.SigningCredentials,
            expires: expiration == null ?
                (DateTime?)null :
                now.Add(expiration.Value)
        );

        return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
    }

Startup.cs:

public class Startup
{
    private readonly string SPA_ROOT_PATH = "ClientApp";
    private readonly string SPA_SOURCE_PATH = "dist";

    public Startup(
        IConfiguration configuration
        )
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {

        services.AddControllers();
        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = SPA_ROOT_PATH;
        });

        services.AddScoped<IAuthenticationService, AuthenticationService>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAuthenticationService authenticationService)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();
        app.UseSpaStaticFiles();
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });

        authenticationService.Authenticate(new Models.TokenAuth.AuthenticateModel
        {
            UserNameOrEmailAddress = Configuration["Authentication:UserNameOrEmailAddress"],
            Password = Configuration["Authentication:Password"]
        });

        app.UseSpa(spa =>
        {
            if (env.IsDevelopment())
            { 
                spa.Options.SourcePath = $"{SPA_ROOT_PATH}/";
                spa.UseVueCli(npmScript: "serve");
            }
            else
            { 
                spa.Options.SourcePath = SPA_SOURCE_PATH;
            }
        });
    }
}
  • What is your product version? 8.7.0./.*.
  • What is your product type (Angular or MVC)? MVC
  • What is product framework type (.net framework or .net core)? .NET Core

Hi,

When trying to create a new tenant with its own connection string, it shows a popup with "internal server error". This does not happen when trying to create one with the host connection string. The specified database connection string is an empty database (no tables), but the error happens when I try it with a database that has tables in it already as well.

Upon debugging TenantManager.cs, it happens specifically when executing _abpZeroDbMigrator.CreateOrMigrateForTenant(tenant)inside CreateWithAdminUserAsync(...).

It happens specifically inside CreatOrMigrateForTenant(...), because it never arrives at the using (...) that follows. I cannot debug this part because it is considered external code. The code hasn't been altered in any way, so it is the one that was provided with version 8.7.0.

When the popup shows up, the database is filled with entities, so it must happen sometime later inside that method, but before the using(...). <br>

// Create tenant database
_abpZeroDbMigrator.CreateOrMigrateForTenant(tenant); // <-- during this the pop-up shows up in the UI and then the next breakpoint at using won't happen

//We are working entities of new tenant, so changing tenant filter
using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
{
    //Create static roles for new tenant
    ...
}

Any help would be appreciated.

Hi,

Thank you for the information. I've re-done the merge conflicts and fixed that part along with it. It works now.

Thank you.

Prerequisites

  • What is your product version? Currently upgraded to 10.1.0; originally 8.7.0.
  • What is your product type (Angular or MVC)? MVC.
  • What is product framework type (.net framework or .net core)? .NET Core.

I've followed the guide found at https://docs.aspnetzero.com/en/common/latest/Version-Updating and merged it to my development and resolved the conflicts as instructed on the linked page, but when I try to build the solution, it fails with 3 errors that always pop-up (the last ones in the screenshot just appeared today):

I do have .NET 5.0 SDK installed, so that shouldn't be an issue either.

Any help would be appreciated.

Hi,

The version used is 8.7.0.

We're currently trying to extend the chat functionality with a custom SignalR hub. Hoever, my colleague and I came across a very weird issue also illustrated in this thread: https://support.aspnetzero.com/QA/Questions/9080/Javascript-Error The difference being that we didn't manage to encounter any exception using Postman, yet all properties of global variable abp cannot be found. The two properties we're trying to access are abp.services.app and abp.signalr.

I've attached a screenshot to illustrate how logging the abp to the console inside my partial view displays all the available and filled properties. But then, the next line of code (so no in-between tampering or mutation), it says it cannot call startConnection() because abp.signalr is undefined all of a sudden.

The startConnection() method call is executed as such and we've mapped the hub in Startup.cs.

<script type="text/javascript">
    var registrationChatHub = null;
    var abp = abp;
    console.log('ABP contents:', abp);

    abp.signalr.startConnection(abp.appPath + 'signalr-chat-registration', function (connection) {
        registrationChatHub = connection; // save reference

        connection.on('getMessage', function (message) {
            console.log('received msg:', message);
        });
    }).then(function (connection) {
        abp.log.debug('Connected to RegistrationHub server.');
        abp.event.trigger('registrationHub.connected');
    });

    abp.event.on('registrationHub.connected', function () {
        registrationChatHub.invoke('sendMessage', 'Hello!');
    });
</script>

Any help would be greatly appreciated.

Hello @ismcagdas

According to your own F.A.Q. Can I use all Metronic components ant themes in ASP.NET Zero? your answer is: ASP.NET Zero uses Metronic as the UI theme. Metronic is one of the world's most popular, strongest and richest UI themes, probably even the first. That's why we have chosen it. When you buy ASP.NET Zero, you will have access to all Metronic theme layouts and components. You can use all of them in your applications.

But it is not the case because Kanban is not implemented even though it is an component inside Metronic see the link in our previous question. When trying to implement this library globally because we want to use it on multiple pages the submenu's aren't working anymore.

So the question remains how to implement kanban globally in our project like the component from Metronic? And why isn't it already incorporated like stated in the F.A.Q.?

Hello, We are trying to implement kanban in our project - ASP.Net core MVC so that we can make better use of the drag and drop functionalities. Reference: https://keenthemes.com/metronic/preview/demo12/components/extended/kanban-board.html We could see that the ASP.Net zero makes use of the Metronic for UI customization. Could you please let us know if there are any NPM or any other way to implement the Kanban in the ASP.Net zero application

The problem with the GetAll is that we are not logged in yet and the menu is determined on launch not on login. So there is no connection with the database. It seems that we need to somehow add a dynamic menu service but I'm a bit confused how to implement this. Do you have any examples for this?

Hi,

We are trying to Add Items to our menu from a database entitie. If we try to getAll we get the exeption that the user was not logged in. And if you login the menu is already loaded so it does not get the call to the database.

As an example we created the Entitie Car so in the menu we have Car but as a submenu we would like to see all the cars that are available inside the database. Car Audi BMW Tesla

How can we create this type of menu dynamically?

We are using the latest version of ASP.Net Zero ASP.Net Core MVC 7.2.0

Hello @ismcagdas,

I'll use the workaround in the meantime, whilst waiting for the new release.

Thanks for the help and quick reply! :)

Showing 11 to 20 of 22 entries