Activities of "mkinc"

Thanks. That is a very manual way of doing it, but perhaps the best way. We have since decided to pivot on our approach to one that we prefer that does not require OTP so I haven't confirmed whether this worked for us.

Morning. Any suggestions on this please?

After further investigation, immediately after the loginUsingGrant, this.authService.isAuthenticated returns true and the access_token that was returned is added to the local storage, alongside access_token_stored_at and expires_at. I can navigate to the user's dashboard (which has an auth guard on it), and in there it also finds this.authService.isAuthenticated returns true. But if I make a query to the public backend, the backend throws Unauthorized, and if I manually refresh the page, this.authService.isAuthenticated is false and the access_token is suddenly removed from local storage.

I can see the Authorization header is set to Bearer XXX from the access_token.

Any tips?

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;

namespace MyCompany.MyProject.PasswordlessLogin;

/// <summary>
///     Inspired by https://abp.io/community/articles/how-to-add-a-custom-grant-type-in-openiddict.-6v0df94z
/// </summary>
public class PasswordlessExtensionGrant : ITokenExtensionGrant
{
    public const string ExtensionGrantName = "PasswordlessAuth";
    private readonly IdentityUserManager _userManager;
    private readonly AbpSignInManager _signInManager;

    public PasswordlessExtensionGrant(IdentityUserManager userManager, AbpSignInManager signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }

    public async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
    {
        var email = context.Request.GetParameter("email")?.ToString();
        var token = context.Request.GetParameter("token")?.ToString();

        var user = await _userManager.FindByEmailAsync(email);
        if (user is null)
        {
            throw new EntityNotFoundException(typeof(IdentityUser));
        }

        var isValid = await _userManager.VerifyUserTokenAsync(user, tokenProvider: "PasswordlessLoginProvider",
            purpose: "passwordless-auth", token);
        if (!isValid)
        {
            throw new UnauthorizedAccessException("The token " + token + " is not valid for the user " +
                                                  email);
        }

        await _userManager.UpdateSecurityStampAsync(user);
        var principal = await _signInManager.CreateUserPrincipalAsync(user);
        principal.SetScopes(principal.GetScopes());
        principal.SetResources(await GetResourcesAsync(context, principal.GetScopes()));
        return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal,
            new OpenIdConnectChallengeProperties()
            {
                AllowRefresh = true, // doesn't seem to return a refresh token
                IsPersistent = true,
            });
    }

    public string Name => ExtensionGrantName;

    private static 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've had some success. I've been able to write a PasswordlessExtensionGrant : ITokenExtensionGrant that takes email and token request params, verifies the token against the user and calls:

        var principal = await _signInManager.CreateUserPrincipalAsync(user);
        return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal);

This actually returns an access token when I call from the front end:

    let result = await this.authService.loginUsingGrant('passwordless-auth2', {token: token, email: email},);
{
    "access_token": "...",
    "token_type": "Bearer",
    "expires_in": 299
}

The problem I'm left with is how I do use the access_token in the response to actually login the user in the angular app? When using a password login, it saves the access_token in the local storage (perhaps amongst other things). I haven't been able to see where in abp code it does that, to be able to mimic something similar.

My second concern is that I don't have a refresh_token, so it won't be able to periodically gain new access_tokens.

Thanks. Let me try and get back to you. Please leave this thread open til at least next weekend.

  • ABP Framework version: v7.3.3
  • UI Type: Angular
  • Database System: EF Core (SQL Server)
  • Tiered (for MVC) or Auth Server Separated (for Angular): Auth Server Separated

I am trying to implement a system where the user is sent a OTP to their email address that they can use to login without needing their password. This article doesn't quite follow our use case. Our solution has:

  • Main ABP Backend (not relevant)
  • Main ABP Front end (not relevant)
  • Custom public site back end (not relevant)
  • Custom public site front end
  • Standard ABP Separated Auth Server (this serves both the main ABP front end and the custom public site front end

This is what I've done so far, based on the article mentioned above:

  1. Followed steps 1-3
  2. Implemented my own endpoint that sends a OTP to the user's email address as an alternative login flow, taking inspiration from step 4, and manually creating an angular proxy to call this endpoint.
  3. Added a page which the email has a link to that calls another endpoint that's from step 7, passing in the token and email address to attempt login.

It all works up until my stage 3, including validating the OTP token and updating the user's security stamp, but the SignInManager.SignInAsync(user, isPersistent: false) call doesn't log the user into our public site (where this endpoint is being called from) according to angular AuthService.IsAuthenticated. I've also tried using other authenticationMethods, such as OidcConstants.AuthenticationMethods.OneTimePassword, but without success.

What the SignInAsync method does do is provide a Set-Cookie for .AspNetCore.Identity.Application.

Any tips on how to progress? Cheers.

My AppService (which is wrapped in a Controller with HttpPost and Route("login") attributes):

using System;
using System.Threading.Tasks;
using IdentityModel;
using MyCompany.MyProject.Email;
using Microsoft.AspNetCore.Authorization;
using OpenIddict.Abstractions;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;

namespace MyCompany.MyProject.PasswordlessLogin;

public class PasswordlessLoginAppService : ApplicationService, IPasswordlessLoginAppService
{
    private readonly IMyProjectAuthServerEmailManager _emailManager;
    private readonly IdentityUserManager _userManager;
    private readonly AbpSignInManager _signInManager;

    public PasswordlessLoginAppService(IMyProjectAuthServerEmailManager emailManager,
        IdentityUserManager userManager, AbpSignInManager signInManager)
    {
        _emailManager = emailManager;
        _userManager = userManager;
        _signInManager = signInManager;
    }

    // [AllowAnonymous]
    public async Task SendOtpEmail(SendOtpEmailInputDto input)
    {
        var user = await _userManager.FindByEmailAsync(input.Email);
        if (user is null)
        {
            throw new EntityNotFoundException(typeof(IdentityUser));
        }

        var token = await _userManager.GenerateUserTokenAsync(user, tokenProvider: "PasswordlessLoginProvider",
            purpose: "passwordless-auth");
        await _emailManager.SendOtpEmailAsync(new SendOtpEmailInput()
        {
            Email = user.Email,
            Token = token,
        });
    }

    public async Task Login(PasswordlessLoginInputDto input)
    {
        var user = await _userManager.FindByEmailAsync(input.Email);
        if (user is null)
        {
            throw new EntityNotFoundException(typeof(IdentityUser));
        }
        var isValid = await _userManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", input.Token);
        if (!isValid)
        {
            throw new UnauthorizedAccessException("The token " + input.Token + " is not valid for the user " + input.Email);
        }

        await _userManager.UpdateSecurityStampAsync(user);

        await _signInManager.SignInAsync(user, isPersistent: false, authenticationMethod: OidcConstants.AuthenticationMethods.OneTimePassword);
    }
}

OK thanks. Do you know which version this will be fixed in?

On three occasions I've experienced cases where on submission of a new ABP support question I get an error on the ABP support website. I'm sorry that I don't have a print screen or anything. I think the behaviour occurs once I raise a question, then immediately after submission click the 'Add question' or something button and raise another question rather than starting from scratch and using the top level question button. If you're unable to replicate let me know and please allow me to create a few dummy questions to see if I can diagnose the problem as it's incredibly frustrating since you lose the question that you composed! Thanks.

Allow form prop extensions for dynamic properties to actually be dynamic on create forms

Is there an existing issue for this?

  • [X] I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

As a developer I want to be able to use the record field in PropPredicate and PropCallback properties on Create forms So that I can dynamically set, for example, the disabled property based on the value of other properties that are currently set in the create form

Describe the solution you'd like

The syntax would remain identical to how it is now, only that it would actually work. It should be such that the documentation can be changed to say that the record field (and others?) are available on create forms (e.g. create user). https://docs.abp.io/en/abp/latest/UI/Angular/Dynamic-Form-Extensions

For example, a new extra property that I add to the form can be disabled or enabled based on whether the user has checked IsActive or not.

https://github.com/abpframework/abp/issues/17374

  • ABP Framework version: Replicable on ABP commercial demo on 14/8/23: ABP v7.4.0. Updated on 2023-08-02 12:30 . Angular Version v16.0.6
  • UI Type: Angular
  • Database System: Unknown
  • Tiered (for MVC) or Auth Server Separated (for Angular): Unknown
  • Steps to reproduce the issue:
    • Log in as admin
    • Create a new user 'test1'
    • Logout
    • Login as test1
    • Set up 2FA authenticator app and enable 2FA
    • Logout
    • Login as test1 and confirm 2FA works as expected (without checking remember browser)
    • Logout
    • Login as admin
    • Edit test1 user to enable 'Should change password on next login'
    • Logout
    • In login page, enter credentials for test1 user
  • Expected behaviour: Before asking for a new password, 2FA should be completed.
  • Actual behaviour:
    • I am asked for current password, new password, new password (repeat) and after submitting that I can login without any 2FA.
    • In order to confirm 2FA is still forced, logout, login again and you will be correctly be asked for 2FA
  • This is a critical security bug where 2FA can be bypassed even if the 2FA is forced.

Please let us know when this will be fixed + refund the question. Cheers.

Showing 1 to 10 of 26 entries
Made with ❤️ on ABP v9.2.0-preview. Updated on January 20, 2025, 07:44