Activities of "alexander.nikonov"

I have tried to use this approach in my HttpInteceptor (instead of handleLinkLoginError, when it's "too late"):

catchError((error: HttpErrorResponse) => {
    if (error.status === 400 && error.url?.includes('/connect/token') && error.error?.error === 'access_denied') {
        return EMPTY;
    }
    return throwError(() => error);
})

In this case, no extra requests (like "/abp/application-configuration") come through as expected. On the other hand, I do not see the original error toaster, because I do not propagate the error. Besides - i am even not sure it would be a right way: maybe reacing on /connect/token like this (even preventing further requests) is an incorrect behavior... In fact, after catching the error like this, I observed some issues when logging in with an active user afterwards.

I've tried this, but it does not work right yet. First of all, this still fails when res is null (when the link login is unsuccessful in my case): localStorage.setItem('access_token', res.access_token);

But even if i prevent this by using:

    private handleLinkLoginError = (err: HttpErrorResponse) => {
        this.toaster.error(err.error?.error_description);
        return EMPTY;
    };

there is an (unnecessary) chain of API requests to finally stay under the same user - prior to handleLinkLoginError: there is API request to 'abp/application-configuration' and so on - that usually take place when you visit an ordinary page (i have no idea why this is happening in this scenario).

Instead, the logic in my understanding should be as simple as follows: if the linked user is denied - current UI user should see the corresponding error toaster and nothing extra should happen (no page reloads or other API calls), the UI user should stay at the same page. Maybe for that, the specific logic needs to be implemented on server-side in a different way - not via IOpenIddictServerHandler?

Hi.

Yes, this is a custom property, but it's not very important here. What is important is to be able to invalidate inactive user not only in LoginModel - when a user tries to log in from Login form (it does work as expected) implemented as follows:

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(LoginModel), typeof(OpenIddictSupportedLoginModel), typeof(AbxLoginModel))]
public class AbxLoginModel : OpenIddictSupportedLoginModel, ITransientDependency
{
    ...

    public override async Task<IActionResult> OnPostAsync(string action)
    {
        AbxLoginModel supportedLoginModel = this;
        var user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ?? await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);

        if (user != null)
        {
            var abxUser = await _abxUserRepository.GetAsync(user.Id);
            if (abxUser != null)
            {
                var dateNow = DateTime.Now.Date;
                if (abxUser.ValidFrom.HasValue && dateNow < abxUser.ValidFrom.Value)
                {
                    Alerts.Danger(_stringLocalizer.GetString("Login:InvalidDateForUser"));
                    supportedLoginModel.EnableLocalLogin = true;
                    return supportedLoginModel.Page();
                }
                if (abxUser.ValidTo.HasValue && dateNow > abxUser.ValidTo.Value)
                {
                    Alerts.Danger(_stringLocalizer.GetString("Login:InvalidDateForUser"));
                    supportedLoginModel.EnableLocalLogin = true;
                    return supportedLoginModel.Page();
                }
            }
        }

        var result = await base.OnPostAsync(action);
        return result;
    }
}

but also when a 'Linked accounts' dialog is shown and a user clicks a linked account login href to login as this user.

Unfortunately, I cannot share the project.

8.1.3 / OpenIDServer / Angular

I have noticed that an inactive login can successfully login from "Linked accounts" dialog. To fix this, I have added the following code into OpenIDServer project:

    public class AbxValidateUserStatusHandler : IOpenIddictServerHandler<ProcessSignInContext>, ITransientDependency
    {
        private readonly IdentityUserManager _userManager;
        private readonly IAbxUserRepository _abxUserRepository;
        private readonly IStringLocalizer<CentralToolsResource> _stringLocalizer;
    
        public AbxValidateUserStatusHandler
        (
            IdentityUserManager userManager,
            IStringLocalizer<CentralToolsResource> stringLocalizer,
            IAbxUserRepository abxUserRepository
        )
        {
            _userManager = userManager;
            _stringLocalizer = stringLocalizer;
            _abxUserRepository = abxUserRepository;
        }
    
        public async ValueTask HandleAsync(ProcessSignInContext context)
        {
            var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value;
            if (!string.IsNullOrWhiteSpace(userId))
            {
                var user = await _userManager.FindByIdAsync(userId);
    
                var abxUser = await _abxUserRepository.GetAsync(user.Id);
                if (abxUser != null)
                {
                    var dateNow = DateTime.Now.Date;
                    if (abxUser.ValidFrom.HasValue && dateNow < abxUser.ValidFrom.Value)
                    {
                        context.Reject
                        (
                            error: OpenIddictConstants.Errors.AccessDenied,
                            description: _stringLocalizer.GetString("Login:InvalidDateForUser")
                        );
                        return;
                    }
                    if (abxUser.ValidTo.HasValue && dateNow > abxUser.ValidTo.Value)
                    {
                        context.Reject
                        (
                            error: OpenIddictConstants.Errors.AccessDenied,
                            description: _stringLocalizer.GetString("Login:InvalidDateForUser")
                        );
                        return;
                    }
                }
            }
        }
    }
    

Basically it works. But not very nice: A) the '/connect/token' response returns code 400 and the following JSON:

    {
      "error": "access_denied",
      "error_description": "This user name is not valid anymore. Please contact your system-administrator"
    }

But error_description is ignored and I only see this: I guess this is the reason:

    this.handleLinkLoginError = (err) => {
            this.toaster.error(err.error?.error); // should be err.error?.error_description ... as everywhere
            return of(null);
    };

B) afterwards, the following ABP method returns js error, because res is null:

    linkLogin(input) {
        return this.identityLinkLoginService.linkLogin(input).pipe(catchError(this.handleLinkLoginError), switchMap(res => {
        if (res.tenant_domain) { // res is NULL!

C) my API calls on home page are invoked (they should not, because nothing has changed - i did not switch to another login) - probably i need to add additional check in my HttpInterceptor to stop this:

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (this.isLoggingOut && !req.url.endsWith('/connect/revocat')) { // too bad we need to hardcode the exact url for logging out
            return EMPTY;
        }
        //another check to handle unsuccessful linked login?

Thank you for the input. I will keep this in mind.

I am trying to retrieve repository using a primitive factory. And getting a proxy wrapper class instead:

Unable to cast object of type 'Castle.Proxies.IBasicRepository1Proxy_52' to type 'AbxFileManagers.IFileRepository1[AbxFileManagers.IFileEntity]'.

I do not want to inject all the repositories into class constructor, because there are too many of them. Also I saw the recommendation to disable audit logging for my entities - that would allegedly eliminate proxy creation. I do not want it either, I do want to keep audit logging things.

Interface:

public interface IFileRepository<TFile> : IRepository<TFile, Guid> where TFile : class, IFileEntity

Implementation:

[ExposeServices(typeof(IFileRepository<>))]
public class EfCoreFileRepository<TFile> : EfCoreRepository<CoreDbContext, TFile, Guid>, IFileRepository<TFile> where TFile : class, IFileEntity

Factory:

public class FileRepositoryFactory : IScopedDependency
{
    private readonly IServiceProvider _serviceProvider;

    public FileRepositoryFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IFileRepository<TFile> GetRepository<TFile>(...) where TFile : class, IFileEntity
    {
        ...

        var repositoryType = typeof(IFileRepository<>).MakeGenericType(entityType);

        return (IFileRepository<TFile>)_serviceProvider.GetRequiredService(repositoryType);
    }
}

One of the classes:

public class FixCharacterLargeObject : FixCharacterLargeObjectEntity, IFileEntity

Its interface:

public interface IFileEntity : IEntity<Guid>, IMayHaveCreatorName, IMayHaveLastModifierName, IHasCreationTime, IHasModificationTime

I use a single generic class EfCoreFileRepository, because all the IFileEntityentities share the same methods, I did not want to duplicate the code. I also tried to make EfCoreFileRepository abstract and inherit from it, but the error is the same.

If this is the best way to accomplish the goal - please let me know (and return the point - I will close the ticket):

    public dynamic GetRepository(AbxFileLocation location, AbxFileType type)
    {
        var repositoryType = typeof(IFileRepository&lt;&gt;).MakeGenericType(AbxFileEntityFactory.GetEntityType(location, type));
    
        var repository = _serviceProvider.GetRequiredService(repositoryType);
    
        if (repository is Castle.DynamicProxy.IProxyTargetAccessor proxyAccessor)
        {
            return proxyAccessor.DynProxyGetTarget(); // Return the real repository implementation
        }
    
        return repository;
    }

Both in the current ABP test solution I use for the demo (version 8.1.3) and in your latest link the same layout is used and thus there is an intermediate step, using "Login" link in the main app. It might look a very subtle nuance, but it makes a big difference, i.e. if AFTER logout but BEFORE logging in, you will go to the login form in BOTH tab - you will get the same error as me (click "Login" button in both tabs to try to login from Login form of OpenID server): As I mentioned, we do not have this very "Login" button in our application: after a user clicks logout, he needs to be redirected straight to the Login form of OpenID server.

The corresponding exception for ABP test solution is different in this case, but still relates to AntiForgery too:

Antiforgery token validation failed. The antiforgery cookie token and request token do not match. Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery cookie token and request token do not match. at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet) at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)

@sumeyye.kurtulus today I easily reproduced the problem on the ABP test project. Essenially, unlike ABP test project, we do not have a login form or link as a part of the application. So after clicking logout link, a user is redirected straight to the common Login page offered by OpenID server. I think the issue with 'storage' event subscription is now explained easily, because Login page is no longer a part of the application, it is a part of OpenID server. But we need the solution for this very same scenario. https://drive.google.com/file/d/11j-ruM0et75fN27etFdo-igstWO8lHup/view?usp=sharing

Yes - that's what I am planning to do. But I do not control Antiforgery token in any way: all the job is done automatically by ABP framework, including the name, the way it is stored, etc. The only relevant code we are now using is:

    Configure&lt;AbpAntiForgeryOptions&gt;(options =>
    {
        options.TokenCookie.Expiration = TimeSpan.FromDays(365);
        // options.AutoValidateIgnoredHttpMethods.Add("POST");
        options.AutoValidate = false;
    })

So my question is how to make Antiforgery token be stored in a session storage of a browser in the first place?

Thank you for sharing more details. However, I did not see anything that would cause this cyclical re-login problem in these files. That is why, I suspected whether you have implemented another guard on the home route.

I cannot reproduce the cyclic reload scenario now. Probably I just forgot to remove a Model.Filters.Add(new IgnoreAntiforgeryTokenAttribute()); last time and "cyclic reloading" on login is what happens next when the AntiForgery thing is resolved.

Anyway, I would prefer to leave the AntiForgery protection as it is.

So now the question is: why is the event handler you suggested - this.window.addEventListener('storage', (event) => { ... }) is not fired for the passive tab. As we found out, Permission Guards does not affect this. Do you have any idea what else I could check?

FYI: there are no other addEventListener in our code. Logout is standard: calling ABP method this.authService.logout()

Thank you very much.

UPDATE. Trying to figure out the problem per se. From what I can see, XSRF-TOKEN is stored in Cookies (and Cookies are identical for both tabs - Angular app in tab #1 and OpenID server login page in tab #2). When I log-in in the tab #1 - the new AntiForgery token is generated and written to Cookies. Tab #2 tries to reuse it (?) and fails, because this token is already "in use". So if I for instance switch to storing AntiForgery token in Session Storage (which is different for both tabs) - it might resolve my problem?

Showing 31 to 40 of 395 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.1.0-preview. Updated on October 30, 2025, 06:33