diff --git a/.gitignore b/.gitignore index 2c00528..bef91c2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ dist-ssr # bun *.tsbuildinfo + +# environment files +.env \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 0c258a6..2b4d4bd 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index b5d02ad..130c4bb 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "prepare": "husky" }, "dependencies": { + "@azure/msal-browser": "^3.23.0", + "@azure/msal-react": "^2.0.22", "@kvib/react": "4.5.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-router": "1.58.3", diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 3450f06..36d0a56 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -2,21 +2,31 @@ import { Header } from "@/components/header"; import { Flex, FooterInline, KvibProvider } from "@kvib/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createRootRoute, Outlet } from "@tanstack/react-router"; +import { MsalAuthenticationTemplate, MsalProvider } from "@azure/msal-react"; +import { InteractionType } from "@azure/msal-browser"; +import { authenticationRequest, msalInstance } from "@/services/msal"; const queryClient = new QueryClient(); export const Route = createRootRoute({ component: () => ( - - -
- -
- -
- - + + + + +
+ + + + + + ), }); diff --git a/src/services/backend.ts b/src/services/backend.ts index 9c9fc6f..518d338 100644 --- a/src/services/backend.ts +++ b/src/services/backend.ts @@ -1,15 +1,30 @@ import { array, number, object, string, type z } from "zod"; +import { msalInstance } from "./msal"; const BACKEND_URL = import.meta.env.VITE_BACKEND_URL ?? "http://localhost:8080"; -const BEARER_TOKEN = import.meta.env.BEARER_TOKEN ?? "test123"; + +function getIdToken() { + const accounts = msalInstance.getAllAccounts(); + const account = accounts[0]; + if (!account) { + throw new Error("No active account"); + } + + const a = account.idToken; + if (!a) { + throw new Error("No id token"); + } + return a; +} // backend fetcher that appends the Bearer token to the request async function fetchFromBackend(path: string, options: RequestInit) { + const idToken = getIdToken(); const response = await fetch(`${BACKEND_URL}${path}`, { ...options, headers: { ...options.headers, - Authorization: `Bearer ${BEARER_TOKEN}`, + Authorization: `Bearer ${idToken}`, }, }); if (!response.ok) { diff --git a/src/services/msal.ts b/src/services/msal.ts new file mode 100644 index 0000000..8527a6f --- /dev/null +++ b/src/services/msal.ts @@ -0,0 +1,47 @@ +import { + type Configuration, + type PopupRequest, + PublicClientApplication, + type RedirectRequest, + type SsoSilentRequest, +} from "@azure/msal-browser"; + +export const clientId = import.meta.env.VITE_CLIENT_ID; +const authority = import.meta.env.VITE_AUTHORITY; +const redirectUri = import.meta.env.VITE_LOGIN_REDIRECT_URI; + +if (!clientId) { + throw new Error("Client ID is not set"); +} +if (!authority) { + throw new Error("Authority is not set"); +} +if (!redirectUri) { + throw new Error("Redirect URI is not set"); +} + +// MSAL configuration +const configuration: Configuration = { + auth: { + clientId, + authority, + redirectUri, // Points to window.location.origin. You must register this URI on Microsoft Entra admin center/App Registration. + postLogoutRedirectUri: "/", // Indicates the page to navigate after logout. + navigateToLoginRequestUrl: false, // If "true", will navigate back to the original request location before processing the auth code response. + }, + cache: { + cacheLocation: "localStorage", // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO between tabs. + storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge + }, +}; + +export const msalInstance = new PublicClientApplication(configuration); + +export const scopes = import.meta.env.VITE_AUTH_SCOPES?.split(",") ?? []; + +export const authenticationRequest: + | PopupRequest + | RedirectRequest + | SsoSilentRequest = { + scopes, +};