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_tokenerror_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
POST /connect/token(password grant) returns 200 and returnsaccess_token/refresh_token.- The SPA/client immediately calls business APIs in parallel.
- Those APIs return 401
SessionExpireddue toIdentitySessionCheckernot finding thesession_idinAbpSessions. - Manual DB inspection sometimes shows the
session_idrow 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:
OpenIddictCreateIdentitySessioninserts the Identity Session row intoAbpSessions.- OpenIddict then writes the token response body to the client.
- The ambient request UnitOfWork commit (so the inserted
AbpSessionsrow 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 officialOpenIddictCreateIdentitySessioninside 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/tokenstill returns 200 normally, but the immediate next API calls no longer fail withSessionExpired.- This confirms the issue is a timing/commit visibility problem between token response output and IdentitySession persistence.
ABP version
10.5.0
4 Answer(s)
-
0
- Your mitigation is the correct and safest workaround for ABP 10.5.0: force the identity session to be persisted/committed inside the
ProcessSignInpipeline before OpenIddict writes the/connect/tokenresponse. - 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_idto be present inAbpSessions. If the token response is returned before the ambient ABP Unit Of Work is completed, the next request can race and hitIdentitySessionCheckerbefore theAbpSessionsinsert is visible/committed.
Recommended implementation (keep your approach)
- Replace/wrap the built-in
OpenIddictCreateIdentitySessionhandler with a custom handler that:- opens a new UOW (
requiresNew: true) - is transactional (
isTransactional: true) - calls the original handler
- calls
CompleteAsync()immediately
- opens a new UOW (
Your sample code matches that pattern; keep it.
- 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 forProcessSignIn).
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) - Your mitigation is the correct and safest workaround for ABP 10.5.0: force the identity session to be persisted/committed inside the
-
0
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) -
0
Hi,
We fixed this on the framework side. The request unit of work is now completed before the
/connectresponse 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/connectby 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) -
0
That’s great.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)