| @@ -0,0 +1,192 @@ | |||||
| # F5 BIG-IP WAF — Login / Refresh `accessToken` follow-up | |||||
| Date: 2026-10-28 | |||||
| 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. | |||||
| ## Goal | |||||
| `/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`. | |||||
| The WAF must **not**: | |||||
| - Replace the body with an HTML block / challenge page | |||||
| - Strip or alter the `accessToken` field | |||||
| - Rewrite long base64url JWT strings in JSON | |||||
| ## App-side mitigation (already done) | |||||
| Frontend changes stop a permanent stuck white screen: | |||||
| - JWT payload decode uses base64url (not raw `atob` on `-` / `_`) | |||||
| - Do not store a missing / `"undefined"` access token | |||||
| - Force clear session and redirect to `/login` when token is unusable | |||||
| App mitigation does **not** make a WAF-damaged response succeed. WAF/LB still needs tuning so login/refresh return a real JWT. | |||||
| ## API paths to protect (PNSPS) | |||||
| Base context path: `/api` (see backend `application.yml`). | |||||
| | Purpose | Method | Path (after host) | | |||||
| |---------|--------|-------------------| | |||||
| | Password login | POST | `/api/login` | | |||||
| | Refresh token | POST | `/api/refresh-token` | | |||||
| | Logout | POST | `/api/logout` | | |||||
| | iAM Smart login | POST | `/api/smart/login` | | |||||
| | iAM Smart direct login | POST | `/api/smart/directLogin` | | |||||
| Confirm exact URLs in browser **Network** tab if your reverse-proxy path differs. | |||||
| --- | |||||
| ## F5 BIG-IP WAF — step by step | |||||
| Menu names vary by BIG-IP / ASM / Advanced WAF version. Use the closest equivalent. | |||||
| ### 1. Confirm which policy sits in front of PNSPS | |||||
| 1. Log in to the BIG-IP UI. | |||||
| 2. Go to **Local Traffic → Virtual Servers**. | |||||
| 3. Open the virtual server for `pnsps.gld.gov.hk` (or the VS that terminates TLS for the site). | |||||
| 4. Note the attached **Application Security Policy** (ASM) / security policy name. | |||||
| 5. Also check whether **Bot Defense**, **DoS**, or other profiles are attached — those can inject HTML challenges. | |||||
| ### 2. Find what was blocked when `accessToken` became `"undefined"` | |||||
| 1. Go to **Security → Event Logs → Application → Requests** | |||||
| (or **Security → Application Security → Event Logs**). | |||||
| 2. Filter around the time of a failed login or blank dashboard. | |||||
| 3. Look for: | |||||
| - `/api/login` | |||||
| - `/api/refresh-token` | |||||
| - `/api/smart/login` / `/api/smart/directLogin` if using iAM Smart | |||||
| 4. Open the event and record: | |||||
| - **Support ID** | |||||
| - **Violation** name (signature, illegal meta character, evasion, bot challenge, etc.) | |||||
| - Whether the **response** was a block / challenge page instead of JSON | |||||
| That violation name is what you exception. | |||||
| ### 3. Ensure API URLs exist in the policy | |||||
| 1. Open the security policy → **URLs** (or **Policy → URLs**). | |||||
| 2. Ensure these URLs exist (exact path as in Network tab), for example: | |||||
| - `/api/login` | |||||
| - `/api/refresh-token` | |||||
| - `/api/smart/login` | |||||
| - `/api/smart/directLogin` | |||||
| 3. Method: **POST**. Prefer a **JSON** content profile if the policy uses content profiles. | |||||
| ### 4. Disable response rewriting for these APIs | |||||
| In the policy, review anything that inspects or rewrites **responses**: | |||||
| 1. **Data Guard** / **Response scrubbing** / **Information leakage** | |||||
| - Disable for `/api/*`, or exclude the URLs above. | |||||
| 2. **Response signatures** / checks that modify the body | |||||
| - Prefer alarm-only on responses for API URLs, or exclude them. | |||||
| 3. **Custom / attack signatures** that false-positive on JWT-looking JSON values | |||||
| - JWTs contain `-`, `_`, `.` and long base64url strings. | |||||
| - From the event log (step 2), **Learn / Allow / Exception** that signature for that URL only. | |||||
| ### 5. Add a targeted exception (preferred once violation is known) | |||||
| From the blocked request in the event log: | |||||
| 1. Open the request → **Accept / Learn** or **Create exception**. | |||||
| 2. Scope narrowly: | |||||
| - **URL** = `/api/login` and/or `/api/refresh-token` (and smart login URLs if needed) | |||||
| - Prefer **this signature + this URL**, not disabling the whole policy | |||||
| 3. Apply / publish the policy. | |||||
| ### 6. Bot Defense / challenge pages | |||||
| If the log shows a bot challenge or CAPTCHA HTML page on login/refresh: | |||||
| 1. Open **Security → Bot Defense** (or the policy Bot profile). | |||||
| 2. Whitelist / exception: | |||||
| - `/api/login` | |||||
| - `/api/refresh-token` | |||||
| - smart login paths if needed | |||||
| 3. Those endpoints must return **`application/json`** from the app, not an HTML interstitial. | |||||
| ### 7. Transparent vs Blocking (UAT validation only) | |||||
| 1. On a **UAT** policy clone, briefly use **Transparent** (or transparent blocking). | |||||
| 2. Reproduce login / stay on dashboard through refresh. | |||||
| 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**. | |||||
| 4. Do **not** leave production in transparent as the long-term fix. | |||||
| ### 8. Verify end-to-end | |||||
| 1. Browser → log in → **Network** → **login** response: | |||||
| - `Content-Type: application/json` | |||||
| - Body contains long `accessToken` and `refreshToken` strings (three JWT segments each) | |||||
| 2. Wait for refresh near expiry (or trigger refresh) → same checks on **refresh-token**. | |||||
| 3. Console: | |||||
| ```javascript | |||||
| localStorage.getItem('accessToken') | |||||
| ``` | |||||
| Must **never** be the literal `"undefined"`. | |||||
| --- | |||||
| ## Message for the F5 admin (copy/paste) | |||||
| > 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. | |||||
| --- | |||||
| ## App verification checklist (after frontend deploy) | |||||
| | Test | Expected | | |||||
| |------|----------| | |||||
| | Normal password login + logout | Dashboard OK; clean logout | | |||||
| | Console: plant `accessToken` = `eyJhbGciOiJIUzUxMiJ9.Pj4-.sig` without `userData`, reload, wait 15s | No repeating `InvalidCharacterError`; redirect to `/login` | | |||||
| | Console: plant `accessToken` = `"undefined"` with `userData`, reload | Within ~10s redirect to `/login`; session keys cleared | | |||||
| | Login/refresh body missing `accessToken` (WAF case) | Stay on login / fail page or force logout — **not** permanent white screen | | |||||
| | iAM Smart success | Still works with real JWT | | |||||
| ### Console helpers | |||||
| Plant base64url payload that old `atob` rejected: | |||||
| ```javascript | |||||
| localStorage.removeItem('userData'); | |||||
| localStorage.setItem('accessToken', 'eyJhbGciOiJIUzUxMiJ9.Pj4-.sig'); | |||||
| location.reload(); | |||||
| ``` | |||||
| Plant stuck `"undefined"` session: | |||||
| ```javascript | |||||
| localStorage.setItem('userData', JSON.stringify({id:1,type:'IND',role:'primary'})); | |||||
| localStorage.setItem('accessToken', 'undefined'); | |||||
| location.reload(); | |||||
| ``` | |||||
| Cleanup: | |||||
| ```javascript | |||||
| localStorage.removeItem('accessToken'); | |||||
| localStorage.removeItem('userData'); | |||||
| location.reload(); | |||||
| ``` | |||||
| --- | |||||
| ## Responsibility split | |||||
| | Layer | Role | | |||||
| |-------|------| | |||||
| | Frontend | Do not store bad tokens; force logout instead of stuck white screen | | |||||
| | F5 WAF / LB | Do not damage login/refresh JSON so `accessToken` is present and valid | | |||||
| ## Related frontend files | |||||
| - `src/components/RefreshTokenProvider.js` — base64url decode + force logout on bad token | |||||
| - `src/auth/index.js` — `isAccessTokenUsable`, `forceLocalLogoutRedirect`, login/refresh guards | |||||
| - `src/auth/jwt/jwtService.js` — same guard on JwtService 401 refresh | |||||
| - Login forms / iAM Smart callbacks — skip success navigate when token unusable | |||||
| @@ -4,12 +4,27 @@ import {isUserLoggedIn} from "../utils/Utils"; | |||||
| import jwtApplicationConfig from "auth/jwtApplicationConfig"; | import jwtApplicationConfig from "auth/jwtApplicationConfig"; | ||||
| import jwt_decode from "jwt-decode"; | import jwt_decode from "jwt-decode"; | ||||
| import {apiPath} from "./utils"; | import {apiPath} from "./utils"; | ||||
| import { | |||||
| refreshIntervalName, | |||||
| predictProductionQty, | |||||
| predictUsageCount, | |||||
| windowCount, | |||||
| isAccessTokenUsable, | |||||
| forceLocalLogoutRedirect, | |||||
| } from "./tokenSession"; | |||||
| // Re-export for existing callers | |||||
| export { | |||||
| refreshIntervalName, | |||||
| predictProductionQty, | |||||
| predictUsageCount, | |||||
| windowCount, | |||||
| isAccessTokenUsable, | |||||
| clearLocalSession, | |||||
| forceLocalLogoutRedirect, | |||||
| } from "./tokenSession"; | |||||
| //import axios from "axios"; | //import axios from "axios"; | ||||
| export const refreshIntervalName = 'refreshInterval' | |||||
| export const predictProductionQty = 'predictProductionQty' | |||||
| export const predictUsageCount = 'predictUsageCount' | |||||
| export const windowCount = 'windowCount' | |||||
| import {useNavigate} from "react-router-dom"; | import {useNavigate} from "react-router-dom"; | ||||
| import {useDispatch} from "react-redux"; | import {useDispatch} from "react-redux"; | ||||
| import { LOGOUT, REFRESH_TOKEN } from 'utils/ApiPathConst'; | import { LOGOUT, REFRESH_TOKEN } from 'utils/ApiPathConst'; | ||||
| @@ -33,6 +48,9 @@ export const clearExpiredSessionAlert = () => { | |||||
| // ** Handle User Login | // ** Handle User Login | ||||
| export const handleLogin = data => { | export const handleLogin = data => { | ||||
| return dispatch => { | return dispatch => { | ||||
| if (!isAccessTokenUsable(data.accessToken)) { | |||||
| return; | |||||
| } | |||||
| dispatch({ | dispatch({ | ||||
| type: 'LOGIN', | type: 'LOGIN', | ||||
| @@ -200,10 +218,19 @@ export const SetupAxiosInterceptors = () => { | |||||
| if (response.status === 200) { | if (response.status === 200) { | ||||
| const newAccessToken = response.data.accessToken; | const newAccessToken = response.data.accessToken; | ||||
| const newRefreshToken = response.data.refreshToken; | const newRefreshToken = response.data.refreshToken; | ||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| isRefreshToken = false; | |||||
| window.location.reload(); | |||||
| if ( | |||||
| isAccessTokenUsable(newAccessToken) && | |||||
| typeof newRefreshToken === 'string' && | |||||
| newRefreshToken | |||||
| ) { | |||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| isRefreshToken = false; | |||||
| window.location.reload(); | |||||
| } else { | |||||
| isRefreshToken = false; | |||||
| forceLocalLogoutRedirect(); | |||||
| } | |||||
| } | } | ||||
| }) | }) | ||||
| .catch((refreshError) => { | .catch((refreshError) => { | ||||
| @@ -273,10 +300,18 @@ export const handleRefreshTokenFunction = () => { | |||||
| if (response.status === 200) { | if (response.status === 200) { | ||||
| const newAccessToken = response.data.accessToken; | const newAccessToken = response.data.accessToken; | ||||
| const newRefreshToken = response.data.refreshToken; | const newRefreshToken = response.data.refreshToken; | ||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| // token = newAccessToken; | |||||
| isRefresh = false; | |||||
| if ( | |||||
| isAccessTokenUsable(newAccessToken) && | |||||
| typeof newRefreshToken === 'string' && | |||||
| newRefreshToken | |||||
| ) { | |||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| isRefresh = false; | |||||
| } else { | |||||
| isRefresh = false; | |||||
| forceLocalLogoutRedirect(); | |||||
| } | |||||
| } else { | } else { | ||||
| // token = null; | // token = null; | ||||
| isRefresh = false; | isRefresh = false; | ||||
| @@ -1,5 +1,6 @@ | |||||
| import axios from 'axios' | import axios from 'axios' | ||||
| import jwtDefaultConfig from './jwtDefaultConfig' | import jwtDefaultConfig from './jwtDefaultConfig' | ||||
| import { isAccessTokenUsable, forceLocalLogoutRedirect } from '../tokenSession' | |||||
| export default class JwtService { | export default class JwtService { | ||||
| // ** jwtConfig <= Will be used by this service | // ** jwtConfig <= Will be used by this service | ||||
| @@ -57,11 +58,21 @@ export default class JwtService { | |||||
| .then(r => { | .then(r => { | ||||
| this.isAlreadyFetchingAccessToken = false | this.isAlreadyFetchingAccessToken = false | ||||
| // ** Update accessToken in localStorage | |||||
| this.setToken(r.data.accessToken) | |||||
| this.setRefreshToken(r.data.refreshToken) | |||||
| this.onAccessTokenFetched(r.data.accessToken) | |||||
| const newAccessToken = r.data.accessToken | |||||
| const newRefreshToken = r.data.refreshToken | |||||
| if ( | |||||
| isAccessTokenUsable(newAccessToken) && | |||||
| typeof newRefreshToken === 'string' && | |||||
| newRefreshToken | |||||
| ) { | |||||
| // ** Update accessToken in localStorage | |||||
| this.setToken(newAccessToken) | |||||
| this.setRefreshToken(newRefreshToken) | |||||
| this.onAccessTokenFetched(newAccessToken) | |||||
| } else { | |||||
| this.onAccessTokenFetchFailed(new Error('Invalid access token from refresh')) | |||||
| forceLocalLogoutRedirect() | |||||
| } | |||||
| }) | }) | ||||
| .catch(refreshError => { | .catch(refreshError => { | ||||
| this.isAlreadyFetchingAccessToken = false | this.isAlreadyFetchingAccessToken = false | ||||
| @@ -0,0 +1,33 @@ | |||||
| /** Session token helpers — keep free of imports from auth/index or jwtService (avoids circular init). */ | |||||
| export const refreshIntervalName = 'refreshInterval' | |||||
| export const predictProductionQty = 'predictProductionQty' | |||||
| export const predictUsageCount = 'predictUsageCount' | |||||
| export const windowCount = 'windowCount' | |||||
| /** JWT-shaped access token (blocks missing/`undefined` from WAF-damaged responses). */ | |||||
| export const isAccessTokenUsable = (t) => | |||||
| typeof t === 'string' && | |||||
| t !== 'undefined' && | |||||
| t !== 'null' && | |||||
| t.split('.').length === 3; | |||||
| /** Clear session keys without calling logout API (bad/missing token cases). */ | |||||
| export const clearLocalSession = () => { | |||||
| localStorage.removeItem('userData') | |||||
| localStorage.removeItem('accessToken') | |||||
| localStorage.removeItem('refreshToken') | |||||
| localStorage.removeItem('axiosToken') | |||||
| localStorage.removeItem('webtoken') | |||||
| localStorage.removeItem('transactionid') | |||||
| localStorage.removeItem('searchCriteria') | |||||
| localStorage.removeItem(refreshIntervalName) | |||||
| localStorage.removeItem(windowCount) | |||||
| localStorage.removeItem(predictProductionQty) | |||||
| localStorage.removeItem(predictUsageCount) | |||||
| }; | |||||
| export const forceLocalLogoutRedirect = () => { | |||||
| clearLocalSession(); | |||||
| window.location.assign('/login'); | |||||
| }; | |||||
| @@ -5,6 +5,7 @@ import axios from 'axios'; | |||||
| import { | import { | ||||
| isUserLoggedIn, | isUserLoggedIn, | ||||
| } from "utils/Utils"; | } from "utils/Utils"; | ||||
| import { isAccessTokenUsable, forceLocalLogoutRedirect } from 'auth/tokenSession'; | |||||
| const RefreshTokenContext = createContext(); | const RefreshTokenContext = createContext(); | ||||
| const RefreshTokenProvider = ({ children }) => { | const RefreshTokenProvider = ({ children }) => { | ||||
| @@ -24,10 +25,20 @@ const RefreshTokenProvider = ({ children }) => { | |||||
| if (response.status === 200) { | if (response.status === 200) { | ||||
| const newAccessToken = response.data.accessToken; | const newAccessToken = response.data.accessToken; | ||||
| const newRefreshToken = response.data.refreshToken; | const newRefreshToken = response.data.refreshToken; | ||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| token.current = newAccessToken; | |||||
| isRefresh.current = false; | |||||
| if ( | |||||
| isAccessTokenUsable(newAccessToken) && | |||||
| typeof newRefreshToken === 'string' && | |||||
| newRefreshToken | |||||
| ) { | |||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| token.current = newAccessToken; | |||||
| isRefresh.current = false; | |||||
| } else { | |||||
| token.current = null; | |||||
| isRefresh.current = false; | |||||
| forceLocalLogoutRedirect(); | |||||
| } | |||||
| } else { | } else { | ||||
| token.current = null; | token.current = null; | ||||
| isRefresh.current = false; | isRefresh.current = false; | ||||
| @@ -48,6 +59,11 @@ const RefreshTokenProvider = ({ children }) => { | |||||
| const checkTokenExpiry = useCallback(() => { | const checkTokenExpiry = useCallback(() => { | ||||
| // Check if token is present and its expiry time | // Check if token is present and its expiry time | ||||
| if (token.current) { | if (token.current) { | ||||
| if (!isAccessTokenUsable(token.current)) { | |||||
| token.current = null; | |||||
| forceLocalLogoutRedirect(); | |||||
| return; | |||||
| } | |||||
| let tokenExp; | let tokenExp; | ||||
| try { | try { | ||||
| // JWT payload is base64url; atob needs standard base64 | // JWT payload is base64url; atob needs standard base64 | ||||
| @@ -58,6 +74,7 @@ const RefreshTokenProvider = ({ children }) => { | |||||
| tokenExp = JSON.parse(atob(payload)).exp; | tokenExp = JSON.parse(atob(payload)).exp; | ||||
| } catch (e) { | } catch (e) { | ||||
| token.current = null; | token.current = null; | ||||
| forceLocalLogoutRedirect(); | |||||
| return; | return; | ||||
| } | } | ||||
| const currentTime = Math.floor(Date.now() / 1000); | const currentTime = Math.floor(Date.now() / 1000); | ||||
| @@ -75,7 +92,7 @@ const RefreshTokenProvider = ({ children }) => { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| }, [token]); | |||||
| }, [token, handleRefreshToken]); | |||||
| // Start the timer on component mount | // Start the timer on component mount | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -28,7 +28,7 @@ import {POST_LOGIN} from "utils/ApiPathConst"; | |||||
| import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | ||||
| import axios from "axios"; | import axios from "axios"; | ||||
| import {useDispatch} from "react-redux"; | import {useDispatch} from "react-redux"; | ||||
| import {handleLogin} from "auth/index"; | |||||
| import {handleLogin, isAccessTokenUsable} from "auth/index"; | |||||
| import {FormattedMessage} from "react-intl"; | import {FormattedMessage} from "react-intl"; | ||||
| // ============================|| FIREBASE - LOGIN ||============================ // | // ============================|| FIREBASE - LOGIN ||============================ // | ||||
| @@ -90,6 +90,11 @@ const AuthLogin = () => { | |||||
| // ability.update(abilities) | // ability.update(abilities) | ||||
| const data = {...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken} | const data = {...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken} | ||||
| if (!isAccessTokenUsable(response.data.accessToken)) { | |||||
| loginInFlightRef.current = false; | |||||
| setLoginRequestPending(false); | |||||
| return; | |||||
| } | |||||
| dispatch(handleLogin(data)) | dispatch(handleLogin(data)) | ||||
| navigate('/dashboard'); | navigate('/dashboard'); | ||||
| //history.push(getHomeRouteForLoggedInUser("user")) | //history.push(getHomeRouteForLoggedInUser("user")) | ||||
| @@ -40,7 +40,7 @@ import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | |||||
| // import axios from "axios"; | // import axios from "axios"; | ||||
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { handleLogin, clearExpiredSessionAlert } from "auth/index"; | |||||
| import { handleLogin, clearExpiredSessionAlert, isAccessTokenUsable } from "auth/index"; | |||||
| import useJwt from "auth/jwt/useJwt"; | import useJwt from "auth/jwt/useJwt"; | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import { SysContext } from "components/SysSettingProvider" | import { SysContext } from "components/SysSettingProvider" | ||||
| @@ -112,6 +112,13 @@ const AuthLoginCustom = () => { | |||||
| const data = { ...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken } | const data = { ...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken } | ||||
| // setSuccess(true) | // setSuccess(true) | ||||
| // console.log(response.data); | // console.log(response.data); | ||||
| if (!isAccessTokenUsable(response.data.accessToken)) { | |||||
| loginInFlightRef.current = false; | |||||
| setOnLogin(false) | |||||
| setErrorMassage("CONNECTION_ERROR") | |||||
| setOpen(true) | |||||
| return; | |||||
| } | |||||
| if (response.data.type === "GLD") { | if (response.data.type === "GLD") { | ||||
| // setLocale("en"); | // setLocale("en"); | ||||
| localStorage.setItem('locale', 'en'); | localStorage.setItem('locale', 'en'); | ||||
| @@ -4,7 +4,7 @@ import * as HttpUtils from "utils/HttpUtils"; | |||||
| import * as UrlUtils from "utils/ApiPathConst"; | import * as UrlUtils from "utils/ApiPathConst"; | ||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { handleLogoutFunction, handleLogin } from 'auth/index'; | |||||
| import { handleLogoutFunction, handleLogin, isAccessTokenUsable } from 'auth/index'; | |||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | ||||
| @@ -69,6 +69,10 @@ const Index = () => { | |||||
| // localStorage.setItem('locale','en'); | // localStorage.setItem('locale','en'); | ||||
| // } | // } | ||||
| // } | // } | ||||
| if (!isAccessTokenUsable(responseData.accessToken)) { | |||||
| window.location.assign("/iamsmart/loginFail"); | |||||
| return; | |||||
| } | |||||
| dispatch(handleLogin(data)) | dispatch(handleLogin(data)) | ||||
| navigate('/iamsmart/loginSucess', { | navigate('/iamsmart/loginSucess', { | ||||
| state: { | state: { | ||||
| @@ -4,7 +4,7 @@ import * as HttpUtils from "utils/HttpUtils"; | |||||
| import * as UrlUtils from "utils/ApiPathConst"; | import * as UrlUtils from "utils/ApiPathConst"; | ||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { handleLogoutFunction, handleLogin } from 'auth/index'; | |||||
| import { handleLogoutFunction, handleLogin, isAccessTokenUsable } from 'auth/index'; | |||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | ||||
| @@ -51,6 +51,10 @@ const Index = () => { | |||||
| //avatar: require('src/assets/images/users/avatar-3.png').default, | //avatar: require('src/assets/images/users/avatar-3.png').default, | ||||
| } | } | ||||
| const data = { ...userData, accessToken: responseData.accessToken, refreshToken: responseData.refreshToken } | const data = { ...userData, accessToken: responseData.accessToken, refreshToken: responseData.refreshToken } | ||||
| if (!isAccessTokenUsable(responseData.accessToken)) { | |||||
| window.location.assign("/iamsmart/loginFail"); | |||||
| return; | |||||
| } | |||||
| dispatch(handleLogin(data)) | dispatch(handleLogin(data)) | ||||
| navigate('/dashboard'); | navigate('/dashboard'); | ||||
| }, | }, | ||||