Learn More, Pay Less!
Limited Time Offer!
Open Closed

Passwordless OPT Grant #8645


User avatar
0
Navneet@aol.com.au created
  • ABP Framework version: v8.2.3
  • UI Type: MVC
  • Database System: EF Core (PostgreSQL) /
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes
  • Exception message and full stack trace:
  • Steps to reproduce the issue:

Hello Abp team,

Could you please guide me on how can I add a PasswordLess OTP grant type in the MVC application?

I have managed to create a checkbox "PasswordLess OTP" in OpenIddictApplication --> Create and Update UI by extending the entity, how to wire up the check box allow PasswordLess OTP grant type

Thanks, Navneet


8 Answer(s)
  • User Avatar
    0
    Navneet@aol.com.au created

    Hi team,

    I found a way to hook it up with UI by overriding the ApplicationAppService. However, I still need help to create "PasswordLess OTP" grant type

    [Authorize(AbpOpenIddictProPermissions.Application.Default)]
    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IApplicationAppService), typeof(ApplicationAppService), typeof(MyApplicationAppService))]
    public class MyApplicationAppService : ApplicationAppService
    {
        public MyApplicationAppService(IOpenIddictApplicationManager applicationManager, 
        IOpenIddictApplicationRepository applicationRepository) : base(applicationManager, applicationRepository)
        {
        }
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

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

  • User Avatar
    0
    Navneet@aol.com.au created

    Thanks, Liangshiwei for sharing the link, it is a useful resource, but you need to go through it to understand.

    1. Just trying to understand, if the custom grant is to validate the token, how can I generate it in the first place?

    2. Also, how to make the login screen remove the password field if the user has opted for passwordless OTP, it needs to be dynamic to allow users with and without PasswordLess OTP

    Thanks, Navneet

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Just trying to understand, if the custom grant is to validate the token, how can I generate it in the first place?

    Here is a simple article https://abp.io/community/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj

    You just need to call this method and then redirect to the return URL. await SignInManager.SignInAsync(user, isPersistent: false);

    Also, how to make the login screen remove the password field if the user has opted for passwordless OTP, it needs to be dynamic to allow users with and without PasswordLess OTP

    You can override the login page.

    abp get-source Volo.Abp.Account.Pro -v 8.2.3

    [ExposeServices(typeof(LoginModel))]
    [Dependency(ReplaceServices = true)]
    public class MyCustomLoginModel : OpenIddictSupportedLoginModel
    {
        public string ClientId {get;set;}
        
        public bool IsOptLogin {get; set } 
    
        public MyCustomLoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IAbpRecaptchaValidatorFactory recaptchaValidatorFactory, IAccountExternalProviderAppService accountExternalProviderAppService, ICurrentPrincipalAccessor currentPrincipalAccessor, IOptions<IdentityOptions> identityOptions, IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions, AbpOpenIddictRequestHelper openIddictRequestHelper) : base(schemeProvider, accountOptions, recaptchaValidatorFactory, accountExternalProviderAppService, currentPrincipalAccessor, identityOptions, reCaptchaOptions, openIddictRequestHelper)
        {
        }
    
        public override async Task<IActionResult> OnGetAsync()
        {
            var openIddictRequest = await OpenIddictRequestHelper.GetFromReturnUrlAsync(base.ReturnUrl);
            ClientId = openIddictRequest.ClientId;
            // check is opt login
            IsOptLogin = ......;
            return await base.OnGetAsync();
        }
    }
    

    Login.cshtml

    @page
    .......
    @using Qa
    @model MyCustomLoginModel
    
    ......
    
    
    @if(IsOptLogin)
    {
    
        ....
        
    }else
    {
        ....
    }
    
    
  • User Avatar
    0
    Navneet@aol.com.au created

    HI liangshiwei,

    Do you mean to use the below to generate a token?

    await UserManager.GenerateUserTokenAsync(adminUser, "PasswordlessLoginProvider","passwordless-auth");
    

    In regards to the custom grant type, I have found the code below in Ticket no. 8041, this is two month old post and has claims and resources of the user.

    Does this code look correct to you for my purpose, or do you think I should remove any unnecessary things as it does not have your suggested code: await SignInManager.SignInAsync(user, isPersistent: false);

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

    Thanks, Navneet

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Do you mean to use the below to generate a token?

    This is a way to generate verification tokens

    await SignInManager.SignInAsync(user, isPersistent: false);

    This code is used to login to the application and generate access_token.

    Does this code look correct to you for my purpose

    I think you don't need to it, you just need to override the login page.

  • User Avatar
    0
    Navneet@aol.com.au created

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

    So I don't need to create a Custom Grant?

    . I just need to override the login page to verify the 6-digit code received by email via UserManager.GenerateUserTokenAsync(adminUser, "PasswordlessLoginProvider","passwordless-auth");

    That should work, am I correct?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    yes, it's right. you can give it a try.

Made with ❤️ on ABP v9.2.0-preview. Updated on February 06, 2025, 11:50