Activities of "wali-092"

I know that you didn't support BFF in ABP until now. but is this task on the Features planned to be implemented or we can implement from our side, and we can push a PR for it ?

we are running out of time because we need to deploy to production and this issue preventing us from this. while we are waiting for the real fix, we need another solution that we can apply in our situation (not the memory storage solution)

unfortunately, we can't apply this solution. please give us timeline for when you will fix this issue on the @abp/ng.oauth ?

Follow-up to #10937: MemoryTokenStorageService + refresh token in memory causes two regressions the previous issue's link is : https://abp.io/support/questions/10937/Angular-MemoryTokenStorageService-still-persists-refreshtoken-in-localStorage-and-concurrent-tabs-break-refresh-token-rotation

Setup: We applied the workaround from this thread. RefreshTokenInMemoryStorageService extends MemoryTokenStorageService, pushes refresh_token into keysShouldStoreInMemory, posts a get for it to the SharedWorker, and removes the old refresh_token from localStorage. It's provided as { provide: MemoryTokenStorageService, useExisting: RefreshTokenInMemoryStorageService } after provideAbpOAuth().

Tokens are no longer in localStorage, as intended, but two problems appeared.

Issue 1: refreshing a page sends the user to the root (dashboard) instead of the page they were on

Steps:

Sign in and open a deep route, e.g. /my-tasks, in a single tab.

Press F5.

Expected: the page reloads on /my-tasks, still signed in.

Actual: the app signs in again through the auth server and lands on / (dashboard). Before the redirect, API calls made during startup return 401.

Our analysis: when the only tab unloads, the SharedWorker has no ports left and the browser ends it, so all in-memory tokens are lost, including the refresh token. The reloaded tab starts anonymous and has to sign in again. When that sign-in starts from a 401 rather than from the guard, no state/returnUrl is sent, so the auth server returns to the redirect URI (root).

Issue 2: duplicating a tab makes all open tabs reload repeatedly

Steps:

Sign in and open /my-tasks. Duplicate the tab (or open the app in a second tab). Expected: the new tab picks up the tokens from the SharedWorker, and the other tabs are not affected. Actual: all open tabs reload several times before they settle.

Network log of the duplicated tab (Fetch/XHR):

Boot 1: openid-configuration, jwks, token, application-configuration (9.2 kB, authenticated), API calls. Boot 2: openid-configuration, jwks, application-configuration (3.4 kB, anonymous). Then the page reloads. Boot 3: openid-configuration, jwks, application-configuration (authenticated), API calls. Our analysis, from the @abp/ng.oauth 10.1.1 sources:

MemoryTokenStorageService asks the SharedWorker for the tokens asynchronously (port.postMessage({ action: 'get' })). Nothing waits for the answers before AuthService.init() and configState.refreshAppState() in getInitialData(). A new tab can therefore load application-configuration without a token and get an anonymous currentUser. The tokens arrive right after that. checkAccessToken (CHECK_AUTHENTICATION_STATE_FN_KEY) then sees hasValidAccessToken() === true with no currentUser.id and calls clearOAuthStorage(). clearOAuthStorage() removes the in-memory keys through the worker. The worker broadcasts remove to every other port, and each tab runs refreshDocument(), which reloads it. The reloaded tabs start with no tokens and sign in again. Each new access_token is broadcast as set, and checkAuthStateChanges reloads any tab whose cache has no access_token yet. This produces the chain of reloads. Questions / requests

Can startup wait for the SharedWorker's answers (e.g. a ready promise in MemoryTokenStorageService awaited before AuthService.init()), so that a new tab never loads the configuration anonymously? Should checkAccessToken avoid clearOAuthStorage(), which signs out every tab, when the mismatch is only caused by the token arriving late in this tab? For example, it could reload the app configuration once with the token instead. Is official support for keeping refresh_token in memory planned? Our workaround relies on private members (keysShouldStoreInMemory, port). What is the recommended way to keep the user on the current page after F5 when the tokens exist only in memory (Issue 1)? Thanks.

Hi maliming

Thanks for the workaround. We'll use it for now.

For our pen-test finding (CWE-922), the cleanest fix is a cookie-based session, where no tokens are held in the browser at all. Could you tell us:

  1. Does ABP have a supported or sample backend-for-frontend (BFF) setup for an Angular SPA in a microservice solution? In this setup, an ASP.NET Core host signs in with OpenIddict, keeps the tokens server-side, gives the browser only an HttpOnly, Secure, SameSite cookie, and proxies API calls (for example with YARP) to the gateway with the access token attached.
  2. If not, is it on the roadmap, and is there a rough timeline?
  3. In the meantime, what is the recommended way to run @abp/ng.core without @abp/ng.oauth? Which services or providers should we replace so that login, logout, the 401 redirect, and /api/abp/application-configuration work with cookie auth?

This would also solve the multi-tab refresh problem, since only the server would redeem refresh tokens, and we could keep SetRefreshTokenReuseLeeway(0).

Thanks

ABP version: 10.1.1 (@abp/ng.core, @abp/ng.oauth), Angular 21.2, angular-oauth2-oidc 20.0.2. Commercial license, microservice solution, Angular SPA, OpenIddict 7.2 auth server.

Background Issue #23930 / PR #24050 moved the access and ID tokens to memory (MemoryTokenStorageService with a SharedWorker). A penetration test of our product found that OAuth tokens kept in browser storage can be stolen through XSS. With 10.1.1 the access token is protected, but refresh_token is not in keysShouldStoreInMemory, so it is still written to localStorage through AbpLocalStorageService. The refresh token is the long-lived credential: with it an attacker can mint new access tokens until it expires. So the original issue is only partly solved.

Problem 1: refresh token in localStorage

  • Steps: default provideAbpOAuth() with the code flow and the offline_access scope. Sign in, then open DevTools → Application → Local Storage.
  • Expected: no long-lived credential in Web Storage.
  • Actual: refresh_token is present in localStorage.

Problem 2: tabs refreshing at the same time break rotation

  • Our OpenIddict server follows current best practice:
    • single-use refresh tokens (SetRefreshTokenReuseLeeway(0));
    • reuse detection that revokes the whole token chain;
    • no sliding expiration.
  • The SharedWorker shares stored tokens between tabs, but each tab still runs its own refresh timer. When two tabs refresh at the same time, the second request presents an already-used refresh token. The server treats it as a replay and revokes the session, and the user is signed out in every tab.

Requests

  1. An option, or a supported extension point, to keep refresh_token in memory as well (shared through the SharedWorker), accepting that the session ends when the last tab closes and is restored by a silent authorization-code redirect using the auth server's cookie.
  2. Make only one tab refresh at a time (run the refresh inside the SharedWorker, or use a cross-tab lock such as the Web Locks API), so rotation with no reuse leeway works with several tabs open.
  3. Longer term: a supported refresh token in an HttpOnly, Secure, SameSite cookie from the auth server, or a documented backend-for-frontend template for Angular, as in the original #23930 proposal.
  4. Documentation:
    • the token storage behaviour in 10.1;
    • that apps overriding OAuthStorage with localStorage lose the protection;
    • the CSP needed for the data: URL SharedWorker (worker-src data:), since a strict worker-src silently turns the shared storage off.

Impact This blocks closing a penetration-test finding (tokens in browser storage, CWE-922) for a government client without replacing ABP's OAuth storage and refresh code.


Want me to remove the two override lines on a front-end branch and test reload, several tabs and the two-tab refresh against dev?

Type: Bug / Security

Environment

ABP Framework: 10.x Frontend: Angular 21 (OAuth2/OIDC via the ABP Angular packages) Authentication server: OpenIddict Token type: JWT access token Access token lifetime: 15 min

Description When a user logs out of the Angular application, the access token should be revoked so it can no longer be used. In our tests, a token copied before logout still works against the APIs after logout.

Steps to reproduce

Log in to the Angular application. Open the browser dev tools (Application → Local Storage) and copy the access_token value. Save the token somewhere outside the browser. Log out from the application. Call a protected API endpoint from Postman with the header Authorization: Bearer .

Expected result The API rejects the token with 401 Unauthorized, because logout revoked it.

Actual result The API accepts the token and returns data as if the user were still logged in.

Impact A token that is leaked or copied (through XSS, a shared machine, or a compromised device) stays usable until it expires, even after the user has explicitly logged out. This defeats the purpose of logout.

Questions for the ABP team

Is this the expected default behavior, for example because access tokens are self-contained JWTs validated locally by the API?

What is the recommended way to make logout invalidate the access token immediately? Options we are considering: Reference access tokens with token entry validation enabled in OpenIddict Calling the revocation endpoint from the Angular client on logout Another approach you recommend

Attachments (available on request): OpenIddict configuration, API logs, minimal reproduction project.

Showing 1 to 7 of 7 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.8.0-preview. Updated on November 11, 2026, 08:10
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.