Open Closed

Identity session inserted during OpenIddict ProcessSignIn is not committed before /connect/token response, causing immediate 401 SessionExpired with Dynamic Claims #10853


User avatar
0
yuxianglong created

Check the docs before asking a question: https://abp.io/docs/latest Check the samples to see the basic tasks: https://abp.io/docs/latest/samples The exact solution to your question may have been answered before, and please first use the search on the homepage.

Provide us with the following info: 🧐 Hint: If you are using the ABP Studio, you can see all the information about your solution from the configuration window, which opens when you right-click on the solution and click on the Solution Configuration button.

  • Exception message and full stack trace:
  • Steps to reproduce the issue:

Summary

With ABP Identity Session Management enabled together with Dynamic Claims, the OAuth password grant endpoint /connect/token can return 200 with a valid access_token (JWT signature/expiry are OK), but the immediately following API request fails with:

  • HTTP 401
  • error=invalid_token
  • error_description=SessionExpired

The failure happens because ABP cannot find the token’s session_id in the AbpSessions table at request time (IdentitySessionChecker log: "Could not find SessionId(...) in the database."). In our observations, the row may appear in AbpSessions shortly after, which suggests a commit/visibility timing issue rather than an invalid JWT.

Expected behavior

After /connect/token returns 200, any immediately subsequent API request using the new access token should pass Identity Session validation when Dynamic Claims is enabled.

Actual behavior

  1. POST /connect/token (password grant) returns 200 and returns access_token / refresh_token.
  2. The SPA/client immediately calls business APIs in parallel.
  3. Those APIs return 401 SessionExpired due to IdentitySessionChecker not finding the session_id in AbpSessions.
  4. Manual DB inspection sometimes shows the session_id row exists later.

Representative log text: "Could not find SessionId(<sid>) in the database." "The token is no longer valid because the user's session expired."

Root cause hypothesis (timing mismatch)

During the same /connect/token HTTP request:

  • OpenIddictCreateIdentitySession inserts the Identity Session row into AbpSessions.
  • OpenIddict then writes the token response body to the client.
  • The ambient request UnitOfWork commit (so the inserted AbpSessions row becomes visible) occurs later in the ASP.NET request pipeline.

As a result, the next HTTP request can start and validate session_id before the inserted row is committed/visible under the DB isolation level.

In some environments, reverse proxies/dev proxies may amplify the issue when the connection is closed after response flush, leading to an aborted request pipeline.

Reproduction outline

Environment conditions we observed:

  • ABP Identity Session Management enabled
  • Dynamic Claims enabled (IsDynamicClaimsEnabled = true)
  • OpenIddict password grant /connect/token
  • Client immediately calls protected APIs after receiving the token
  • Reverse proxy / dev proxy in front may affect timing (e.g. connection close behavior)

Related work / possible overlap

We found ABP Support issue #10850: https://abp.io/support/questions/10850/1050-regression-identity-session-invalid-after-second-re-authentication-in-same-browser-session--IdentitySessionChecker-cannot-find-just-created-SessionId-user-force-logged-out

That report focuses on regression after second re-authentication (sign-out + sign-in) and session revocation behavior in a browser session. Our case occurs right after a successful /connect/token response with an immediate next API call, without relying on the same “second re-auth” flow.

Current mitigation in our project (already deployed / verified)

In our integration, we implemented a mitigation by replacing the default OpenIddict Identity Session handler with a wrapper that commits the Identity Session earlier.

Behavior:

  • When handling OpenIddictServerEvents.ProcessSignIn, we execute the official OpenIddictCreateIdentitySession inside a new transactional UnitOfWork (requiresNew: true, isTransactional: true).
  • We call CompleteAsync(CancellationToken.None) immediately after the official handler runs, before OpenIddict writes the token response to the client.
public class OpenIddictCommitIdentitySessionHandler : IOpenIddictServerHandler<OpenIddictServerEvents.ProcessSignInContext>
{
    public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor
        .CreateBuilder<OpenIddictServerEvents.ProcessSignInContext>()
        .UseScopedHandler<OpenIddictCommitIdentitySessionHandler>()
        .SetOrder(100000)
        .SetType(OpenIddictServerHandlerType.Custom)
        .Build();

    private readonly OpenIddictCreateIdentitySession _createIdentitySession;
    private readonly IUnitOfWorkManager _unitOfWorkManager;

    public OpenIddictCommitIdentitySessionHandler(
        OpenIddictCreateIdentitySession createIdentitySession,
        IUnitOfWorkManager unitOfWorkManager)
    {
        _createIdentitySession = createIdentitySession;
        _unitOfWorkManager = unitOfWorkManager;
    }

    public async ValueTask HandleAsync(OpenIddictServerEvents.ProcessSignInContext context)
    {
        using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);
        await _createIdentitySession.HandleAsync(context);
        await uow.CompleteAsync(CancellationToken.None);
    }
}

Result:

  • /connect/token still returns 200 normally, but the immediate next API calls no longer fail with SessionExpired.
  • This confirms the issue is a timing/commit visibility problem between token response output and IdentitySession persistence.

ABP version

10.5.0

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

4 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • Your mitigation is the correct and safest workaround for ABP 10.5.0: force the identity session to be persisted/committed inside the ProcessSignIn pipeline before OpenIddict writes the /connect/token response.
    • The reason you only see it when Dynamic Claims is enabled is that dynamic claims/session validation happens on the very next request and requires the session_id to be present in AbpSessions. If the token response is returned before the ambient ABP Unit Of Work is completed, the next request can race and hit IdentitySessionChecker before the AbpSessions insert is visible/committed.

    Recommended implementation (keep your approach)

    1. Replace/wrap the built-in OpenIddictCreateIdentitySession handler with a custom handler that:
      • opens a new UOW (requiresNew: true)
      • is transactional (isTransactional: true)
      • calls the original handler
      • calls CompleteAsync() immediately

    Your sample code matches that pattern; keep it.

    1. Ensure your custom handler is actually registered and runs after the original sign-in logic but early enough before response serialization (your .SetOrder(100000) is fine as long as it is effectively executed in the pipeline for ProcessSignIn).

    Why this aligns with ABP conventions

    • ABP’s Unit Of Work is convention-based and typically commits at the end of controller/action scope (or other framework-managed boundaries). OpenIddict’s token endpoint/sign-in processing can end up writing the response before that conventional boundary completes, which is why an explicit “commit now” is needed in this edge case.
    • Dynamic Claims behavior is documented as being applied on each request by the Dynamic Claims middleware, so it’s expected that any session-backed validation must be consistent immediately after token issuance.

    Links (validated)

    • https://abp.io/docs/10.5/framework/fundamentals/dynamic-claims
    • https://abp.io/docs/10.5/framework/architecture/domain-driven-design/unit-of-work
    • https://abp.io/support/questions/10850/1050-regression-identity-session-invalid-after-second-re-authentication-in-same-browser-session--IdentitySessionChecker-cannot-find-just-created-SessionId-user-force-logged-out

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    We'll investigate this and fix it on our side.

    Please stick with your workaround until the fix is released.

    We've refunded your ticket.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    We fixed this on the framework side. The request unit of work is now completed before the /connect response is flushed, so the identity session and the OpenIddict token/authorization rows are committed and visible to the immediate next request. It's turned on for /connect by default, so you don't need to configure anything.

    It'll be included in an upcoming release. Once you upgrade, you can remove your custom OpenIddictCommitIdentitySessionHandler.

    PR: https://github.com/abpframework/abp/pull/26017

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    yuxianglong created

    That’s great.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
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.8.0-preview. Updated on September 28, 2026, 11:44
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.