You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

waf-login-refresh-accessToken.md 7.6 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # F5 BIG-IP WAF — Login / Refresh `accessToken` follow-up
  2. Date: 2026-10-28
  3. Related symptom: after login (or later on the dashboard), `localStorage.accessToken` becomes the string `"undefined"`, white blank screen, user cannot logout until local storage is cleared. Worse with **WAF blocking mode**; usually fine when blocking is off.
  4. ## Goal
  5. `/api/login` and `/api/refresh-token` (and iAM Smart login APIs) must return **untouched JSON** from the backend in blocking mode, with a real JWT in `accessToken` / `refreshToken`.
  6. The WAF must **not**:
  7. - Replace the body with an HTML block / challenge page
  8. - Strip or alter the `accessToken` field
  9. - Rewrite long base64url JWT strings in JSON
  10. ## App-side mitigation (already done)
  11. Frontend changes stop a permanent stuck white screen:
  12. - JWT payload decode uses base64url (not raw `atob` on `-` / `_`)
  13. - Do not store a missing / `"undefined"` access token
  14. - Force clear session and redirect to `/login` when token is unusable
  15. App mitigation does **not** make a WAF-damaged response succeed. WAF/LB still needs tuning so login/refresh return a real JWT.
  16. ## API paths to protect (PNSPS)
  17. Base context path: `/api` (see backend `application.yml`).
  18. | Purpose | Method | Path (after host) |
  19. |---------|--------|-------------------|
  20. | Password login | POST | `/api/login` |
  21. | Refresh token | POST | `/api/refresh-token` |
  22. | Logout | POST | `/api/logout` |
  23. | iAM Smart login | POST | `/api/smart/login` |
  24. | iAM Smart direct login | POST | `/api/smart/directLogin` |
  25. Confirm exact URLs in browser **Network** tab if your reverse-proxy path differs.
  26. ---
  27. ## F5 BIG-IP WAF — step by step
  28. Menu names vary by BIG-IP / ASM / Advanced WAF version. Use the closest equivalent.
  29. ### 1. Confirm which policy sits in front of PNSPS
  30. 1. Log in to the BIG-IP UI.
  31. 2. Go to **Local Traffic → Virtual Servers**.
  32. 3. Open the virtual server for `pnsps.gld.gov.hk` (or the VS that terminates TLS for the site).
  33. 4. Note the attached **Application Security Policy** (ASM) / security policy name.
  34. 5. Also check whether **Bot Defense**, **DoS**, or other profiles are attached — those can inject HTML challenges.
  35. ### 2. Find what was blocked when `accessToken` became `"undefined"`
  36. 1. Go to **Security → Event Logs → Application → Requests**
  37. (or **Security → Application Security → Event Logs**).
  38. 2. Filter around the time of a failed login or blank dashboard.
  39. 3. Look for:
  40. - `/api/login`
  41. - `/api/refresh-token`
  42. - `/api/smart/login` / `/api/smart/directLogin` if using iAM Smart
  43. 4. Open the event and record:
  44. - **Support ID**
  45. - **Violation** name (signature, illegal meta character, evasion, bot challenge, etc.)
  46. - Whether the **response** was a block / challenge page instead of JSON
  47. That violation name is what you exception.
  48. ### 3. Ensure API URLs exist in the policy
  49. 1. Open the security policy → **URLs** (or **Policy → URLs**).
  50. 2. Ensure these URLs exist (exact path as in Network tab), for example:
  51. - `/api/login`
  52. - `/api/refresh-token`
  53. - `/api/smart/login`
  54. - `/api/smart/directLogin`
  55. 3. Method: **POST**. Prefer a **JSON** content profile if the policy uses content profiles.
  56. ### 4. Disable response rewriting for these APIs
  57. In the policy, review anything that inspects or rewrites **responses**:
  58. 1. **Data Guard** / **Response scrubbing** / **Information leakage**
  59. - Disable for `/api/*`, or exclude the URLs above.
  60. 2. **Response signatures** / checks that modify the body
  61. - Prefer alarm-only on responses for API URLs, or exclude them.
  62. 3. **Custom / attack signatures** that false-positive on JWT-looking JSON values
  63. - JWTs contain `-`, `_`, `.` and long base64url strings.
  64. - From the event log (step 2), **Learn / Allow / Exception** that signature for that URL only.
  65. ### 5. Add a targeted exception (preferred once violation is known)
  66. From the blocked request in the event log:
  67. 1. Open the request → **Accept / Learn** or **Create exception**.
  68. 2. Scope narrowly:
  69. - **URL** = `/api/login` and/or `/api/refresh-token` (and smart login URLs if needed)
  70. - Prefer **this signature + this URL**, not disabling the whole policy
  71. 3. Apply / publish the policy.
  72. ### 6. Bot Defense / challenge pages
  73. If the log shows a bot challenge or CAPTCHA HTML page on login/refresh:
  74. 1. Open **Security → Bot Defense** (or the policy Bot profile).
  75. 2. Whitelist / exception:
  76. - `/api/login`
  77. - `/api/refresh-token`
  78. - smart login paths if needed
  79. 3. Those endpoints must return **`application/json`** from the app, not an HTML interstitial.
  80. ### 7. Transparent vs Blocking (UAT validation only)
  81. 1. On a **UAT** policy clone, briefly use **Transparent** (or transparent blocking).
  82. 2. Reproduce login / stay on dashboard through refresh.
  83. 3. If the problem disappears only in transparent mode, the blocking policy is still rewriting or blocking — use event logs to exception those violations, then return to **Blocking**.
  84. 4. Do **not** leave production in transparent as the long-term fix.
  85. ### 8. Verify end-to-end
  86. 1. Browser → log in → **Network** → **login** response:
  87. - `Content-Type: application/json`
  88. - Body contains long `accessToken` and `refreshToken` strings (three JWT segments each)
  89. 2. Wait for refresh near expiry (or trigger refresh) → same checks on **refresh-token**.
  90. 3. Console:
  91. ```javascript
  92. localStorage.getItem('accessToken')
  93. ```
  94. Must **never** be the literal `"undefined"`.
  95. ---
  96. ## Message for the F5 admin (copy/paste)
  97. > PNSPS SPA needs `/api/login` and `/api/refresh-token` (POST, JSON), and if used `/api/smart/login` and `/api/smart/directLogin`, to return the backend body unchanged in WAF **blocking** mode. The WAF must not replace them with an HTML block/challenge page or strip/alter `accessToken`. Please identify the violating signature/URL from ASM Event Logs and add a narrow exception; also disable response Data Guard / bot challenge on those API paths if they rewrite JSON.
  98. ---
  99. ## App verification checklist (after frontend deploy)
  100. | Test | Expected |
  101. |------|----------|
  102. | Normal password login + logout | Dashboard OK; clean logout |
  103. | Console: plant `accessToken` = `eyJhbGciOiJIUzUxMiJ9.Pj4-.sig` without `userData`, reload, wait 15s | No repeating `InvalidCharacterError`; redirect to `/login` |
  104. | Console: plant `accessToken` = `"undefined"` with `userData`, reload | Within ~10s redirect to `/login`; session keys cleared |
  105. | Login/refresh body missing `accessToken` (WAF case) | Stay on login / fail page or force logout — **not** permanent white screen |
  106. | iAM Smart success | Still works with real JWT |
  107. ### Console helpers
  108. Plant base64url payload that old `atob` rejected:
  109. ```javascript
  110. localStorage.removeItem('userData');
  111. localStorage.setItem('accessToken', 'eyJhbGciOiJIUzUxMiJ9.Pj4-.sig');
  112. location.reload();
  113. ```
  114. Plant stuck `"undefined"` session:
  115. ```javascript
  116. localStorage.setItem('userData', JSON.stringify({id:1,type:'IND',role:'primary'}));
  117. localStorage.setItem('accessToken', 'undefined');
  118. location.reload();
  119. ```
  120. Cleanup:
  121. ```javascript
  122. localStorage.removeItem('accessToken');
  123. localStorage.removeItem('userData');
  124. location.reload();
  125. ```
  126. ---
  127. ## Responsibility split
  128. | Layer | Role |
  129. |-------|------|
  130. | Frontend | Do not store bad tokens; force logout instead of stuck white screen |
  131. | F5 WAF / LB | Do not damage login/refresh JSON so `accessToken` is present and valid |
  132. ## Related frontend files
  133. - `src/components/RefreshTokenProvider.js` — base64url decode + force logout on bad token
  134. - `src/auth/index.js` — `isAccessTokenUsable`, `forceLocalLogoutRedirect`, login/refresh guards
  135. - `src/auth/jwt/jwtService.js` — same guard on JwtService 401 refresh
  136. - Login forms / iAM Smart callbacks — skip success navigate when token unusable