Activities of "mkinc"

Question

We have a high priority, elusive, intermittent issue on our prod system where users are getting logged out at some point while using the system while using our "public site". The public site is our own angular front end using ABP backend project + separated ABP Auth Server. The "admin site" is the normal ABP angular front end that we've built on.

There are two noted instances of the issue that I will mention:

  1. A customer was logged out the first time after they logged in that they attempted to get a new access token from their refresh token. We have access tokens that expire in 5 mins, so it makes /connect/token requests every 3 mins 45 seconds or so. What I note about this time is that two /connect/token requests were made at exactly the same time (shown in our AbpAuditLogs at the time that it failed.
  2. I performed an experiment where I had 45 Chrome tabs open, all logged in our prod system as the same user (normal shared local storage etc). I didn't have any issues for 6 hours. I happened to turn a VPN for something else and at that point I got (failed)net::ERR_NETWORK_CHANGED on a /connect/token request on two tabs. It navigated me to the login page. On the remaining 43 tabs, calls to https://<redacted>/connect/logout?post_logout_redirect_uri=https%3A%2F%2F<redacted> were made and they all navigated me to the login page over the next ~5 mins.

In both instances of this issue, I note that there were errors in the Auth server logs, HOWEVER, it's worth noting that when I was performing the experiment, I was seeing this error on prod 14 times per minute, which equates to this happening nearly every time token requests were made (generally volume on prod is not extreme) from my 45 tabs, without me experiencing the logout issue.

An exception occurred in the database while saving changes for context type '"Volo.Abp.OpenIddict.EntityFrameworkCore.OpenIddictProDbContext"'."""Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithRowsAffectedOnlyAsync(Int32 commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.SqlServer.Update.Internal.SqlServerModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)"

I can see some related questions:

  • https://abp.io/support/questions/9561/Randomly-AbpDbConcurrencyException
  • https://abp.io/support/questions/8933/Intermittent-Login-Failure-After-Upgrading-ABP-Framework-to-v9#answer-3a189b11-6314-b51d-08de-b775c7bdf450
  • https://abp.io/support/questions/9604/AbpDbConcurrencyException

However, AbpEfCoreNavigationHelper is not available in ABP v7.3.3. It was added in March 2024 which is after the v7.3 release I believe. Are there alternatives for v7.3.3?

This is our angular auth class that wraps over the abp auth service.

import {AuthService, ConfigStateService, LoginParams} from "@abp/ng.core";
import {Injectable} from '@angular/core';
import {ActivatedRoute, Params} from "@angular/router";
import {firstValueFrom} from "rxjs";
import {<redacted>LocalStorageService} from "../<redacted>-local-storage.service";
import {AuthConfigService} from "./auth-config.service";
import {LoginType} from "./login-type";

const socialLoginCallbackQueryParams = "social-login-callback-query-params";
const impersonationCallbackQueryParams = "impersonation-callback-query-params";

/**
 * Wrapper around AuthService
 * DO NOT USE AuthService directly or it may logout
 * using a login flow that was NOT the one used to log in
 */
@Injectable({
  providedIn: 'root',
})
export class <redacted>AuthService {

  constructor(
    private authService: AuthService,
    private route: ActivatedRoute,
    private localstorage: <redacted>LocalStorageService,
    private authConfigService: AuthConfigService,
    private configStateService: ConfigStateService
  ) {
  }

  get cachedSocialLoginQueryParams(): Params {
    return this.localstorage.getItem(socialLoginCallbackQueryParams);
  }

  get localStorageImpersonationQueryParams(): Params {
    return this.localstorage.getItem(impersonationCallbackQueryParams);
  }

  public async init(loginType?: LoginType): Promise<any> {
    await this.configureAndInitAsync(loginType);
  }

  public async isAuthenticated(): Promise<boolean> {
    await this.configureAndInitAsync();
    return this.authService.isAuthenticated;
  }

  public async logout(): Promise<void> {
    // if we're impersonating, we don't want to log out of the auth server (code) otherwise we can't initiate new
    // impersonation sessions without needing to log in again, but rather just logout just for the public site
    // - thus force password response type
    await this.configureAndInitAsync(this.authConfigService.currentLoginType === LoginType.Impersonation
      ? LoginType.Password : undefined);

    await firstValueFrom(this.authService.logout());
  }

  public async passwordLogin(params: LoginParams): Promise<any> {
    await this.configureAndInitAsync(LoginType.Password);
    return await firstValueFrom(this.authService.login(params));
  }

  public async socialLogin(provider: string): Promise<void> {
    await this.configureAndInitAsync(LoginType.Social);

    let queryParams: Params = await firstValueFrom(this.route.queryParams);
    this.localstorage.setItem(socialLoginCallbackQueryParams, queryParams);

    this.authService.navigateToLogin({
      "IsSocialLogin": true,
      "Provider": provider ?? undefined,
    });
  }

  public async navigateToLoginForImpersonation(queryParams?: Params): Promise<void> {
    await this.configureAndInitAsync(LoginType.Impersonation);
    this.authService.navigateToLogin(queryParams);
  }

  public deleteCachedSocialLoginQueryParams(): void {
    this.localstorage.removeItem(socialLoginCallbackQueryParams);
  }

  public deleteLocalStorageImpersonationQueryParams(): void {
    this.localstorage.removeItem(impersonationCallbackQueryParams);
  }

  public async setLocalStorageImpersonationQueryParams(): Promise<void> {
    let queryParams: Params = await firstValueFrom(this.route.queryParams);
    this.localstorage.setItem(impersonationCallbackQueryParams, queryParams);
  }

  private async configureAndInitAsync(loginType?: LoginType): Promise<void> {
    let hasChanges: boolean = this.authConfigService.configureAuth(loginType);
    if (hasChanges) {
      // Only call 'init' if the auth environment config has changed, since we can't cancel the refresh token
      // subscription. Re-initializing without changes results in extra 'connect/token' requests, leading to errors on
      // the auth server due to concurrency
      await this.authService.init();

      // Refresh app state to reflect any session changes (e.g. newly acquired token)
      this.configStateService.refreshAppState();
    }
  }
}

and a relevant HTTP_INTERCEPTORS, but according to the AuditLogs, neither the customer nor me had a 400 error on connect/token (in my case, it never reached the Auth server and in the customer's case, they were both 200s):

import {Injectable} from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpErrorResponse,
} from '@angular/common/http';
import {Observable, tap} from 'rxjs';
import {EnvironmentService} from "@abp/ng.core";
import {Router} from "@angular/router";
import {<redacted>AuthService} from "./shared/auth/<redacted>-auth.service";

@Injectable()
export class ErrorHandlingInterceptor implements HttpInterceptor {

  private readonly tokenUrl: string;

  constructor(
    private <redacted>AuthService: <redacted>AuthService,
    private router: Router,
    environmentService: EnvironmentService
  ) {
    let issuerUrl = environmentService.getEnvironment()['oAuthConfig']['issuer'];
    this.tokenUrl = this.ensureEndsWithSlash(issuerUrl) + 'connect/token';
  }

  private ensureEndsWithSlash(str: string): string {
    let slash = '/';
    if (str.endsWith(slash)) {
      return str;
    }

    return str + slash;
  }

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      tap({
        next: _ => {
        },
        error: async (err: HttpErrorResponse) => {
          // Account lockout
          if (err.url == this.tokenUrl
            && err.status == 400
            && await this.<redacted>AuthService.isAuthenticated()) {
            await this.<redacted>AuthService.logout()
            await this.router.navigate([''], { queryParamsHandling: 'preserve' });
          }
        }
      }));
  }
}

Do you have any ideas about solving this issue please?

Thanks, Matt

  • 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);
    }
}
  • 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.

Bug raised here https://support.abp.io/QA/Questions/5126/Bug---Should-change-password-on-next-login-should-enforce-password-to-be-different should have been fixed in 'the preview version for 7.3' but the issue is replicable both in v7.3.2 and the ABP commercial v7.4.0 as of 14/8/23.

  • 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

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

  • 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
  • Background issue: https://support.abp.io/QA/Questions/5132/Bugs---Various-issues-with-user-filtering
    • In this question some bugs were identified and should have been fixed. Most have been fixed, except this one (https://support.abp.io/QA/Questions/5582/Bug-Filtering-users-by-Modification-date-does-not-work). But also it now highlights the below issue.
  • Steps to reproduce the issue:
    • Login as admin
    • Go to Administration -> Identity Management -> Users (observe ALL users shown)
    • Open the Advanced Filters expandable.
    • Select the 'Role' as 'admin'
    • Check 'Email confirmed'
    • Click 'Refresh' (observe only admins with emails confirmed are displayed)
    • Uncheck 'Email confirmed'
    • Click 'Refresh' (observe only admins with emails NOT confirmed are displayed)
  • Expected behaviour
      1. The state of filtering is completely encompassed in the filter options that are shown.
      1. There is a way to no longer filter 'Email confirmed', while keeping the 'Role' filter.
  • Actual behaviour
      1. The UI looks the same when filtering by NOT 'Email confirmed' as it does when not filtering by 'Email confirmed'. The UI does not uniquely identify the filtering that is to be applied which is very confusing as a user.
      1. There is no way to set the 'Email confirmed' filtering to be off without losing all other filters.
  • Other issues:
    • The same is replicable for all checkbox filters.
    • Thought needs to go into whether there are other cases of this in ABP tables.
  • Proposed solution: The checkbox is replaced with a dropdown that has three options: '' (the no filtering option which is already present with Roles and other fields), 'True', 'False'.

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

  • 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
  • Background issue: https://support.abp.io/QA/Questions/5132/Bugs---Various-issues-with-user-filtering
    • In this question some bugs were identified and should have been fixed. Most have been fixed, but one has not been fixed.
  • Steps to reproduce the issue:
    • Login as admin
    • Go to Administration -> Identity Management -> Users
    • Open the Advanced Filters expandable.
    • Set the modified date to a single date, or a range of dates, for which you know there are no modifications (e.g. a date in the future).
  • Expected behaviour: No users are shown.
  • Actual behaviour: All users are shown.
  • Note the filtering does work for creation date, but not for modification date.

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

  • ABP Framework version: v7.2.1
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): Separated OpenIdDict auth server
  • Exception message and stack trace:
  • Steps to reproduce the issue:
    • Log in as host admin
    • Create a user with username abc and email john.smith@example.com
    • Create another user with username john.smith@example.com and email john.smith2@example.com
  • Expected behaviour: Error when creating user to say a user already exists with email 'john.smith@example.com'.
  • Actual behaviour: User is created successfully, but the first user with username abc can now no longer log into their account using their email address.

Similar checks need to be made when attempting to change either username or password since no username should be identical with another user's email address (and vice versa). However it should be maintained that the username and email address of an individual user should be able to be the same. Please refund the question. Thanks.

  • ABP Framework version: v7.2.1
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): OpenIdDict separate auth server
  • Exception message and stack trace:
  • Steps to reproduce the issue:
    • Log in as host admin
    • Create user
    • Login as the new user
    • In My account -> Personal info, click Verify next to email.
    • Logout of user
    • Click the link in the email
    • [Observe email has been confirmed which can later be verified in the front end for that user]
    • Click 'Log in to application'
  • Expected behaviour: Goes to login page
  • Actual behaviour: 404 page not found error. The link is to the base of the front end app (e.g. in dev for us it's https://admin.localhost:44388/) but this by itself isn't a valid url. It needs to be https://admin.localhost:44388/Account/Login with all the url params that you'd normally get if you navigated to the login page from the main app 'Login' button

Potential solutions:

  • [Best solution?] Make sure the route of the auth server front end application routes to the login page or something?
  • In EmailConfirmationModel make sure the ReturnUrl is set to the _appUrlProvider.GetUrlAsync("Angular") (e.g. for us that would be something like admin.localhost:4200)

Thanks.

  • ABP Framework version: v7.3.0 - replicable on ABP demo on 24th May 2023.
  • UI type: Angular
  • DB provider: Unknown
  • Tiered (MVC) or Identity Server Separated (Angular): Unknown
  • Steps to reproduce the issue:
    • Login as admin
    • Navigate to Administration -> Identity Management -> Users
      • Observe all users shown in table.
    • Show the Advanced filters
    • Set 'Email confirmed' to on and click 'Refresh'
      • Observe no users correctly displayed
    • Set 'Email confirmed' to off and click 'Refresh'
  • Expected behaviour: All users shown again.
  • Actual behaviour: No users shown (and not even 'No data available' label)
  • Further issues:
    • Same is replicable when using 'Is external'.
    • Issue not rectified when clicking 'Clear'.
    • Filtering by Creation date or Modified date does not appear to work at all (all users always displayed regardless of what creation date or Modified date range is used).
  • ABP Framework version: v7.2.1
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): Separated AuthServer
  • Steps to reproduce the issue:
    • Create user, enabling the 'Should change password on next login'.
    • Login as user
    • When prompted for new password, use the original password for current, new and new (repeat)
  • Expected behaviour: Error on submit / validation error that doesn't allow submit.
  • Actual behaviour: Password is allowed and user is logged in.

I can't really see any justification to suggest this isn't a bug since forcing a user to change password on next login is something used for security. Untested: Does this affect the 'Force users to periodically change password' feature as well?

Showing 1 to 10 of 11 entries
Learn More, Pay Less
33% OFF
All Trainings!
Get Your Deal
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on September 16, 2025, 10:35