Skip to content

Commit

Permalink
🐛 Fix session expiry when refresh tokens are disabled (#1614)
Browse files Browse the repository at this point in the history
Resolves https://issues.redhat.com/browse/MTA-1255

- Since we no longer have to support windup reports in a new tab, this
code can be cleaned up to use keycloaks internal session management

Signed-off-by: ibolton336 <[email protected]>
  • Loading branch information
ibolton336 authored Dec 13, 2023
1 parent e98511e commit eea7835
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 91 deletions.
39 changes: 34 additions & 5 deletions client/src/app/axios-config/apiInit.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
import axios from "axios";
import keycloak from "@app/keycloak";

export const initInterceptors = (getToken: () => Promise<string>) => {
export const initInterceptors = () => {
axios.interceptors.request.use(
async (config) => {
const token = await getToken();
if (token) config.headers["Authorization"] = "Bearer " + token;
(config) => {
const token = keycloak.token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
Promise.reject(error);
return Promise.reject(error);
}
);

axios.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
if (error.response && error.response.status === 401) {
try {
const refreshed = await keycloak.updateToken(5);
if (refreshed) {
const retryConfig = {
...error.config,
headers: {
...error.config.headers,
Authorization: `Bearer ${keycloak.token}`,
},
};
return axios(retryConfig);
}
} catch (refreshError) {
keycloak.login();
}
}
return Promise.reject(error);
}
);
};
98 changes: 15 additions & 83 deletions client/src/app/components/KeycloakProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { initInterceptors } from "@app/axios-config";
import { isAuthRequired } from "@app/Constants";
import i18n from "@app/i18n";
import React, { Suspense } from "react";
import { ReactKeycloakProvider } from "@react-keycloak/web";
import keycloak from "@app/keycloak";
import { deleteCookie, getCookie, setCookie } from "@app/queries/cookies";
import { AppPlaceholder } from "./AppPlaceholder";
import { Flex, FlexItem, Spinner } from "@patternfly/react-core";
import { ReactKeycloakProvider } from "@react-keycloak/web";
import React, { Suspense } from "react";
import { initInterceptors } from "@app/axios-config";

interface IKeycloakProviderProps {
children: React.ReactNode;
Expand All @@ -15,81 +11,17 @@ interface IKeycloakProviderProps {
export const KeycloakProvider: React.FC<IKeycloakProviderProps> = ({
children,
}) => {
const checkAuthCookie = () => {
if (!getCookie("keycloak_cookie") && keycloak?.token) {
setCookie("keycloak_cookie", keycloak.token, 365);
}
};
if (isAuthRequired) {
return (
<>
<ReactKeycloakProvider
authClient={keycloak}
initOptions={{ onLoad: "login-required" }}
LoadingComponent={
<Flex
spaceItems={{ default: "spaceItemsSm" }}
alignItems={{ default: "alignItemsCenter" }}
flexWrap={{ default: "nowrap" }}
style={{
width: "100%",
height: "100%",
}}
>
<FlexItem
style={{
margin: "auto auto",
textAlign: "center",
}}
>
<Spinner>Loading...</Spinner>
</FlexItem>
</Flex>
}
isLoadingCheck={(keycloak) => {
if (keycloak.authenticated) {
initInterceptors(
() =>
new Promise<string>((resolve, reject) => {
if (keycloak.token) {
if (keycloak.refreshToken) {
keycloak
.updateToken(60)
.then(() => {
deleteCookie("keycloak_cookie");
checkAuthCookie();
return resolve(keycloak.token!);
})
.catch((err) => {
console.log("err", err);
return reject("Failed to refresh token");
});
} else return resolve(keycloak.token!);
} else {
keycloak.login();
reject("Not logged in");
}
})
);

const kcLocale = (keycloak.tokenParsed as any)["locale"];
if (kcLocale) {
i18n.changeLanguage(kcLocale);
}
}
React.useEffect(() => {
initInterceptors();
}, []);

return !keycloak.authenticated;
}}
>
{children}
</ReactKeycloakProvider>
</>
);
} else {
return (
<>
<Suspense fallback={<AppPlaceholder />}>{children}</Suspense>
</>
);
}
return (
<ReactKeycloakProvider
authClient={keycloak}
initOptions={{ onLoad: "login-required" }}
LoadingComponent={<AppPlaceholder />}
>
<Suspense fallback={<AppPlaceholder />}>{children}</Suspense>
</ReactKeycloakProvider>
);
};
5 changes: 2 additions & 3 deletions client/src/app/layout/HeaderApp/SSOMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export const SSOMenu: React.FC = () => {
id="sso-actions-toggle"
onClick={() => onDropdownToggle(!isDropdownOpen)}
>
{(keycloak?.idTokenParsed as any)["preferred_username"]}
{(keycloak?.idTokenParsed as any)?.["preferred_username"] ??
"DefaultUsername"}
</MenuToggle>
)}
>
Expand All @@ -63,8 +64,6 @@ export const SSOMenu: React.FC = () => {
id="logout"
key="sso_logout"
onClick={() => {
// Clears selected persona from storage without updating it in React state so we don't re-render the persona selector while logging out.
// We have to clear it before logout because the redirect can happen before the logout promise resolves.
window.localStorage.removeItem(
LocalStorageKey.selectedPersona
);
Expand Down

0 comments on commit eea7835

Please sign in to comment.