Open Closed

How-to-configure-separate-authentication-based-on-user-type. #8041


User avatar
0
smansuri created
  • ABP Framework version: v8.2.1
  • UI Type: Angular / MVC / Blazor Server
  • Database System: EF Core (MySQL)
  • Tiered (for MVC) or Auth Server Separated (for Angular): Auth server separated angular
  • Exception message and full stack trace: NA
  • Steps to reproduce the issue: NA

We have use case where we want to configure authentication scheme based on the user type. if a user is not a backend user/company user but a consumer than we want to configure mobile OTP based authentication and for company/tenant backend user , we want to use user name password authentication. Please suggest how to achieve this.


39 Answer(s)
  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hello ,

    Can you please check the similar issue https://abp.io/support/questions/8010/OTP-based-login-Implementation-Passwordless-login-implementation-guider

    Thank you.

  • User Avatar
    0
    alper created
    Support Team Director

    your ticket is reopened

  • User Avatar
    0
    smansuri created

    Thanks for re-opening the ticket. We are trying to implement OTP based authentication for mobile app. Our users may not necessary have email ids. In the AbpUsers table emailid is not null by design. We would like to make the Emailid column nullable. In any system design there should be flexibility for authentication and by making the emailid not null we are loosing this flexibility. please advise on the same

  • User Avatar
    0
    smansuri created

    Any update on this?

  • User Avatar
    0
    smansuri created

    Hello ,

    Can you please check the similar issue https://abp.io/support/questions/8010/OTP-based-login-Implementation-Passwordless-login-implementation-guider

    Thank you.

    We want to implement this for react native abp mobile app. After implementing this, how do we redirect user back to mobile?. SHall we return the token generated or claims or both?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    We would like to make the Emailid column nullable.

    This may break many functions of the system. You can set the email field to a regular value, eg username@yourdomain.com

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    . if a user is not a backend user/company user but a consumer than we want to configure mobile OTP based authentication and for company/tenant backend user , we want to use user name password authentication. Please suggest how to achieve this.

    You can add a new page to let the user enter the username, then check the user type and redirect it to your OTP or password page.

    You can also do this function on one page. This is based on yours.

  • User Avatar
    0
    smansuri created

    hi

    . if a user is not a backend user/company user but a consumer than we want to configure mobile OTP based authentication and for company/tenant backend user , we want to use user name password authentication. Please suggest how to achieve this.

    You can add a new page to let the user enter the username, then check the user type and redirect it to your OTP or password page.

    You can also do this function on one page. This is based on yours.

    We have created new page in mobile react native app to get the mobile number as user name and verify the OTP. Now we have implemented the password-less-login using https://abp.io/support/questions/8010/OTP-based-login-Implementation-Passwordless-login-implementation-guider if OTP is verified successfully. The confusion here is how to handle or set the permissions from this post request at the mobile side. Do we need to return the token from the post request and use it every time or we need to simply call application-configuration method with userid/mobilenumber from the mobile app?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    The confusion here is how to handle or set the permissions from this post request at the mobile side. Do we need to return the token from the post request and use it every time or we need to simply call application-configuration method with userid/mobilenumber from the mobile app?

    This is based on your authentication scheme.

    Cookies or JWT token?

    You should attach the cookie and jwt token in HTTP request headers.

  • User Avatar
    0
    smansuri created

    hi

    The confusion here is how to handle or set the permissions from this post request at the mobile side. Do we need to return the token from the post request and use it every time or we need to simply call application-configuration method with userid/mobilenumber from the mobile app?

    This is based on your authentication scheme.

    Cookies or JWT token?

    You should attach the cookie and jwt token in HTTP request headers.

    https://abp.io/community/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj This article creates token based on user object with below code : var token = await UserManager.GenerateUserTokenAsync(adminUser, "PasswordlessLoginProvider", "passwordless-auth"); and it does not look like a JWT token. We are getting the reponse headers with cookie like this :

    shall we use application configuration call with this cookie to get permission? or please share us the documentation to proceed further? Can you review the article https://abp.io/community/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj and confirm if we can use it for OTP authentication as is.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You are using the Cookies authentication now.

    So you have to carry the Cookies when you call the API.

    The backend will get user info and permission grants info from cookies.

  • User Avatar
    0
    smansuri created

    hi

    You are using the Cookies authentication now.

    So you have to carry the Cookies when you call the API.

    The backend will get user info and permission grants info from cookies.

    In the same flow can you help us to change the password-less authentication to use JWT bearer token. How to generate the JWT token in the above flow as its more secure. Also as we are implementing this in abp react mobile app cross site cookie issue may come and not work.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    For token authentication, you can use OpenIddict as oauth2 server.

    The built-in template projects have integrated the OpenIddict.

    https://abp.io/docs/latest/modules/openiddict

    Example: use username and password to get a access_token from openiddict

  • User Avatar
    0
    smansuri created

    hi

    For token authentication, you can use OpenIddict as oauth2 server.

    The built-in template projects have integrated the OpenIddict.

    https://abp.io/docs/latest/modules/openiddict

    Example: use username and password to get a access_token from openiddict

    The whole idea behind the ticket is to implement passwordless authentication. We got some idea behind this from the article https://abp.io/community/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj but as its using cookies authentication the existing flow of react native is breaking. in order to continue it we would like to use JWT token authentication with passwordless. so please suggest how to integrate with current middleware.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can create a custom grant type to get a token.

    Pass the passwordless token to the connect/token endpoint to get the token.

    https://abp.io/community/articles/how-to-add-a-custom-grant-type-in-openiddict.-6v0df94z

  • User Avatar
    0
    pvala created

    Hi, I went through the implementation you have provided. Here is what I have done.

    I have two APIs, the first one is a send OTP API, which sends the OTP to the mobile number and the other API validates that OTP and on successful validation of the API it let's the user signs in. We are creating a new user using the Mobile Number as the username of the user and mobile_number@example.com as the Email of the user. If the user is already registered, then we just fetch the user details, otherwise we create the user and let it log in into the application.

    Here's the controller :

    [HttpPost]
    [AllowAnonymous]
    [Route("passwordless-authentication")]
    public async Task<VerifyOTPAndSignInResponseDto> VerifyOTPAndSignIn(VerifyOTPAndSignInInput input)
    {
        using (CurrentTenant.Change(input.TenantId))
        {
            using (var unitOfWork = UnitOfWorkManager.Begin())
            {
                try
                {
                    VerifyOTPResponseDto otpVerification = await VerifyOTP(
                        new VerifyOTPInput()
                        {
                            CountryCode = input.CountryCode,
                            MobileNumber = input.MobileNumber,
                            OTP = input.OTP,
                        });
    
                    if (otpVerification.type == "success")
                    {
                        var existingUser = await UserManager.FindByNameAsync(input.CountryCode + input.MobileNumber);
                        var user = new Volo.Abp.Identity.IdentityUser(GuidGenerator.Create(), input.CountryCode + input.MobileNumber, input.CountryCode + input.MobileNumber + "@example.com", null);
                        user.SetPhoneNumber(input.CountryCode + "-" + input.MobileNumber, true);
    
                        if (existingUser == null)
                        {
                            (await UserManager.CreateAsync(user)).CheckErrors();
                            using (CurrentTenant.Change(null))
                            {
                                (await UserManager.AddToRoleAsync(user, "Patient")).CheckErrors();
                            }
                            await UsersAppService.PasswordlessUserRegistration(new PasswordlessUserRegistration(input.CountryCode, input.MobileNumber, user.Id));
                        }
                        else
                        {
                            user = existingUser;
                        }
    
                        var token = await UserManager.GenerateUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth");
    
                        var isValid = await UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token);
                        if (!isValid)
                        {
                            throw new UnauthorizedAccessException("The token " + token + " is not valid for the user " + user.Id);
                        }
    
                        await UserManager.UpdateSecurityStampAsync(user);
                        await unitOfWork.CompleteAsync();
                        await SignInManager.SignInAsync(user, isPersistent: true);
                        return
                            new VerifyOTPAndSignInResponseDto()
                            {
                                message = otpVerification.message,
                                type = otpVerification.type,
                                token = token
                            };
                    }
                    else
                    {
                        return new VerifyOTPAndSignInResponseDto()
                        {
                            message = otpVerification.message,
                            type = otpVerification.type
                        };
                    }
                }
                catch (Exception e)
                {
                    await unitOfWork.RollbackAsync();
                    unitOfWork.Dispose();
                    throw;
                }
            }
        }
    }
    

    Here the UserManager is our UserManager, that we have overridden.

    Also, this i the file I have added as well,

    using Microsoft.AspNetCore.Identity; using System.Threading.Tasks;

    namespace G1.health.AuthServer.PasswordlessAuthentication;

    public class PasswordlessLoginProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser> where TUser : class { public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user) { return Task.FromResult(false); }

    public async override Task&lt;string&gt; GetUserModifierAsync(string purpose, UserManager&lt;TUser&gt; manager, TUser user)
    {
        var userId = await manager.GetUserIdAsync(user);
    
        return "PasswordlessLogin:" + purpose + ":" + userId;
    }
    

    }

    And this one as well,

    using Microsoft.AspNetCore.Identity;

    namespace G1.health.AuthServer.PasswordlessAuthentication;

    public static class IdentityBuilderExtensions { public static IdentityBuilder AddPasswordlessLoginProvider(this IdentityBuilder builder) { var userType = builder.UserType; var totpProvider = typeof(PasswordlessLoginProvider<>).MakeGenericType(userType); return builder.AddTokenProvider("PasswordlessLoginProvider", totpProvider); } }

    As mentioned in this article.

    https://abp.io/community/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj

    Now, after configuring all these things, I added whatever you asked in this implementation : https://abp.io/community/articles/how-to-add-a-custom-grant-type-in-openiddict.-6v0df94z

    public const string ExtensionGrantName = "PasswordlessAuthentication";

    This is the ExtensionGrantName name that I have given and the same Grant Name I have added in the OpenIddictApplications table as well for the Angular client.

    After that, using postman, I am trying to validate the user and let it log in, but it says, invalid token.

    The token that I am passing that I am deriving from here in my Validating OTP controller :

                        var token = await UserManager.GenerateUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth");
    
                        var isValid = await UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token);
    

    Can you tell me if anything I need to configure or if I am missing something.

  • User Avatar
    0
    pvala created

    Also, the Token that I am getting is not like a JWT Token, it's like a 6 digit number, so I am not sure how it's working in the background.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Please share the full class code of the PasswordlessAuthentication grant.

  • User Avatar
    0
    pvala created

    Sure, here it is.

    
    namespace G1.health.AuthServer.PasswordlessAuthentication;
    
    public class EmpowermTokenExtensionGrant : ITokenExtensionGrant
    {
        public const string ExtensionGrantName = "PasswordlessLoginProvider";
    
        public string Name => ExtensionGrantName;
        public async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
        {
            var userToken = context.Request.GetParameter("token").ToString();
    
            if (string.IsNullOrEmpty(userToken))
            {
                return new ForbidResult(
                    new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest
                    }!));
            }
    
            var transaction = await context.HttpContext.RequestServices.GetRequiredService<IOpenIddictServerFactory>().CreateTransactionAsync();
            transaction.EndpointType = OpenIddictServerEndpointType.Introspection;
            transaction.Request = new OpenIddictRequest
            {
                ClientId = context.Request.ClientId,
                ClientSecret = context.Request.ClientSecret,
                Token = userToken
            };
    
            var notification = new OpenIddictServerEvents.ProcessAuthenticationContext(transaction);
            var dispatcher = context.HttpContext.RequestServices.GetRequiredService<IOpenIddictServerDispatcher>();
            await dispatcher.DispatchAsync(notification);
    
            if (notification.IsRejected)
            {
                return new ForbidResult(
                    new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri
                    }));
            }
    
            var principal = notification.GenericTokenPrincipal;
            if (principal == null)
            {
                return new ForbidResult(
                    new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri
                    }));
            }
    
            var userId = principal.FindUserId();
            var userManager = context.HttpContext.RequestServices.GetRequiredService<EmpowermIdentityUserManager>();
            var user = await userManager.GetByIdAsync(userId.Value);
            var userClaimsPrincipalFactory = context.HttpContext.RequestServices.GetRequiredService<IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser>>();
            var claimsPrincipal = await userClaimsPrincipalFactory.CreateAsync(user);
            claimsPrincipal.SetScopes(principal.GetScopes());
            claimsPrincipal.SetResources(await GetResourcesAsync(context, principal.GetScopes()));
    
            await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>().HandleAsync(context.Request, claimsPrincipal);
    
            return new Microsoft.AspNetCore.Mvc.SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal);
        }
    
        private async Task<IEnumerable<string>> GetResourcesAsync(ExtensionGrantContext context, ImmutableArray<string> scopes)
        {
            var resources = new List<string>();
            if (!scopes.Any())
            {
                return resources;
            }
    
            await foreach (var resource in context.HttpContext.RequestServices.GetRequiredService<IOpenIddictScopeManager>().ListResourcesAsync(scopes))
            {
                resources.Add(resource);
            }
            return resources;
        }
    }
    

    I have updated the ExtensionGrantName value to "PasswordlessLoginProvider", since I am generating and validating the token with the same token provider name (I am not sure if it's necessary), but to avoid any confusion I have done that.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    These code are not compatible with your case.

    You should use UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token) to check the code.

    If the code is correct, you can generate the access_token for the user.

    And there is no user in your token request. You can consider passing a userid in the request.

    var transaction = await context.HttpContext.RequestServices.GetRequiredService<IOpenIddictServerFactory>().CreateTransactionAsync();
    transaction.EndpointType = OpenIddictServerEndpointType.Introspection;
    transaction.Request = new OpenIddictRequest
    {
        ClientId = context.Request.ClientId,
        ClientSecret = context.Request.ClientSecret,
        Token = userToken
    };
    
    var notification = new OpenIddictServerEvents.ProcessAuthenticationContext(transaction);
    var dispatcher = context.HttpContext.RequestServices.GetRequiredService<IOpenIddictServerDispatcher>();
    await dispatcher.DispatchAsync(notification);
    
    if (notification.IsRejected)
    {
        return new ForbidResult(
            new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
            properties: new AuthenticationProperties(new Dictionary<string, string>
            {
                [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri
            }));
    }
    
    var principal = notification.GenericTokenPrincipal;
    if (principal == null)
    {
        return new ForbidResult(
            new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
            properties: new AuthenticationProperties(new Dictionary<string, string>
            {
                [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri
            }));
    }
    
  • User Avatar
    0
    pvala created

    How do I generate the access_token if the code is valid?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Something like this:

    return new Microsoft.AspNetCore.Mvc.SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal); will generate a access token.

    namespace G1.health.AuthServer.PasswordlessAuthentication;
    
    public class EmpowermTokenExtensionGrant : ITokenExtensionGrant
    {
        public const string ExtensionGrantName = "PasswordlessLoginProvider";
    
        public string Name => ExtensionGrantName;
        
        public async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
        {
            var userToken = context.Request.GetParameter("token").ToString();
            if (string.IsNullOrEmpty(userToken))
            {
                return new ForbidResult(
                    new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest
                    }!));
            }
    
            var userId = context.Request.GetParameter("userid").ToString();
            if (string.IsNullOrEmpty(userId))
            {
                return new ForbidResult(
                    new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest
                    }!));
            }
    
            var userManager = context.HttpContext.RequestServices.GetRequiredService<EmpowermIdentityUserManager>();
            var user = await userManager.GetByIdAsync(userId);
    
            if(!await UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token))
            {
                return new ForbidResult(
                    new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest
                    }!));
            }
    
            var userClaimsPrincipalFactory = context.HttpContext.RequestServices.GetRequiredService<IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser>>();
            var claimsPrincipal = await userClaimsPrincipalFactory.CreateAsync(user);
            claimsPrincipal.SetScopes(principal.GetScopes());
            claimsPrincipal.SetResources(await GetResourcesAsync(context, principal.GetScopes()));
    
            await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>().HandleAsync(context.Request, claimsPrincipal);
    
            return new Microsoft.AspNetCore.Mvc.SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal);
        }
    
        private async Task<IEnumerable<string>> GetResourcesAsync(ExtensionGrantContext context, ImmutableArray<string> scopes)
        {
            var resources = new List<string>();
            if (!scopes.Any())
            {
                return resources;
            }
    
            await foreach (var resource in context.HttpContext.RequestServices.GetRequiredService<IOpenIddictScopeManager>().ListResourcesAsync(scopes))
            {
                resources.Add(resource);
            }
            return resources;
        }
    }
    
  • User Avatar
    0
    pvala created

    As you mentioned, I removed the validation of user from my controller where token is getting generated, and now I am validating the token in my grant extension handler method, but the problem is, in the controller, the token gets validated, but the grant extension method it doesn't validate the token.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    but the grant extension method it doesn't validate the token.

    I don't understand.

    You can send the code to user, then pass userid and code to your grant extension

    if the code is correct. Then, return the access token. in this way, you can use the access token to call the api.

  • User Avatar
    0
    pvala created

    This is what I am getting

Made with ❤️ on ABP v9.1.0-preview. Updated on November 11, 2024, 11:11