From dc5bb4084c2649940e13ac03cc8d15cafdf53b1b Mon Sep 17 00:00:00 2001 From: Yuqing Wei Date: Thu, 9 Jun 2022 17:32:34 +0800 Subject: [PATCH 1/4] enable JWT token param in frontend API calls --- ui/src/api/api.tsx | 46 ++++++++++++++++++++++++++++++++++++------ ui/src/app.tsx | 12 +++-------- ui/src/utils/utils.tsx | 12 +++++++++++ 3 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 ui/src/utils/utils.tsx diff --git a/ui/src/api/api.tsx b/ui/src/api/api.tsx index 653a2677a..98826f34e 100644 --- a/ui/src/api/api.tsx +++ b/ui/src/api/api.tsx @@ -1,11 +1,15 @@ import Axios from "axios"; import { DataSource, Feature, FeatureLineage, UserRole, Role } from "../models/model"; +import { InteractionRequiredAuthError, PublicClientApplication } from "@azure/msal-browser"; import mockUserRole from "./mock/userrole.json"; +import { getMsalConfig } from "../utils/utils"; const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT + "/api/v1"; -const token = "mockAppServiceKey"; +const MOCK_TOKEN = "mockAppServiceKey"; +const msalInstance = getMsalConfig(); export const fetchDataSources = async (project: string) => { + const token = await getAccessToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/projects/${ project }/datasources?code=${ token }`, { headers: {} }) @@ -15,6 +19,7 @@ export const fetchDataSources = async (project: string) => { }; export const fetchProjects = async () => { + const token = await getAccessToken(msalInstance); return Axios .get<[]>(`${ API_ENDPOINT }/projects?code=${ token }`, { @@ -26,6 +31,7 @@ export const fetchProjects = async () => { }; export const fetchFeatures = async (project: string, page: number, limit: number, keyword: string) => { + const token = await getAccessToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/projects/${ project }/features?code=${ token }`, { @@ -38,6 +44,7 @@ export const fetchFeatures = async (project: string, page: number, limit: number }; export const fetchFeature = async (project: string, featureId: string) => { + const token = await getAccessToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/features/${ featureId }?code=${ token }`, {}) .then((response) => { @@ -46,6 +53,7 @@ export const fetchFeature = async (project: string, featureId: string) => { }; export const fetchProjectLineages = async (project: string) => { + const token = await getAccessToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/projects/${ project }?code=${ token }`, {}) .then((response) => { @@ -54,6 +62,7 @@ export const fetchProjectLineages = async (project: string) => { }; export const fetchFeatureLineages = async (project: string) => { + const token = await getAccessToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/features/lineage/${ project }?code=${ token }`, {}) .then((response) => { @@ -63,8 +72,9 @@ export const fetchFeatureLineages = async (project: string) => { // Following are place-holder code export const createFeature = async (feature: Feature) => { + const token = await getAccessToken(msalInstance); return Axios - .post(`${ API_ENDPOINT }/features`, feature, + .post(`${ API_ENDPOINT }/features?code=${ token }`, feature, { headers: { "Content-Type": "application/json;" }, params: {}, @@ -76,8 +86,9 @@ export const createFeature = async (feature: Feature) => { } export const updateFeature = async (feature: Feature, id: string) => { + const token = await getAccessToken(msalInstance); feature.guid = id; - return await Axios.put(`${ API_ENDPOINT }/features/${ id }`, feature, + return await Axios.put(`${ API_ENDPOINT }/features/${ id }?code=${ token }`, feature, { headers: { "Content-Type": "application/json;" }, params: {}, @@ -89,8 +100,9 @@ export const updateFeature = async (feature: Feature, id: string) => { }; export const deleteFeature = async (qualifiedName: string) => { + const token = await getAccessToken(msalInstance); return await Axios - .delete(`${ API_ENDPOINT }/features/${ qualifiedName }`, + .delete(`${ API_ENDPOINT }/features/${ qualifiedName }?code=${ token }`, { headers: { "Content-Type": "application/json;" }, params: {}, @@ -107,6 +119,7 @@ export const listUserRole = async () => { }; export const getUserRole = async (userName: string) => { + const token = await getAccessToken(msalInstance); return await Axios .get(`${ API_ENDPOINT }/user/${userName}/userroles?code=${ token }`, {}) .then((response) => { @@ -115,8 +128,9 @@ export const getUserRole = async (userName: string) => { } export const addUserRole = async (role: Role) => { + const token = await getAccessToken(msalInstance); return await Axios - .post(`${ API_ENDPOINT }/user/${role.userName}/userroles/new`, role, + .post(`${ API_ENDPOINT }/user/${role.userName}/userroles/new?code=${ token }`, role, { headers: { "Content-Type": "application/json;" }, params: {}, @@ -128,8 +142,9 @@ export const addUserRole = async (role: Role) => { } export const deleteUserRole = async (role: Role) => { + const token = await getAccessToken(msalInstance); return await Axios - .post(`${ API_ENDPOINT }/user/${role.userName}/userroles/delete`, role, + .post(`${ API_ENDPOINT }/user/${role.userName}/userroles/delete?code=${ token }`, role, { headers: { "Content-Type": "application/json;" }, params: {}, @@ -139,3 +154,22 @@ export const deleteUserRole = async (role: Role) => { return error.response; }); } + +export const getAccessToken = async( msalInstance: PublicClientApplication ): Promise => { + const activeAccount = msalInstance.getActiveAccount(); // This will only return a non-null value if you have logic somewhere else that calls the setActiveAccount API + const accounts = msalInstance.getAllAccounts(); + const request = { + scopes: ["User.Read"], + account: activeAccount || accounts[0] + }; + await msalInstance.acquireTokenSilent(request).then(response => { + return response.accessToken + }).catch(error => { + if (error instanceof InteractionRequiredAuthError) { + msalInstance.acquireTokenPopup(request).then(response => { + return response.accessToken + }); + } + }) + return MOCK_TOKEN +} \ No newline at end of file diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 2a21eb373..3abae7935 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -2,7 +2,7 @@ import React from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import { Layout } from "antd"; import { QueryClient, QueryClientProvider } from "react-query"; -import { Configuration, InteractionType, PublicClientApplication, } from "@azure/msal-browser"; +import { InteractionType } from "@azure/msal-browser"; import { MsalAuthenticationTemplate, MsalProvider } from "@azure/msal-react"; import Header from "./components/header/header"; import SideMenu from "./components/sidemenu/siteMenu"; @@ -15,17 +15,11 @@ import Monitoring from "./pages/monitoring/monitoring"; import LineageGraph from "./pages/feature/lineageGraph"; import Management from "./pages/management/management"; import RoleManagement from "./pages/management/roleManagement"; +import { getMsalConfig } from "./utils/utils"; const queryClient = new QueryClient(); -const msalConfig: Configuration = { - auth: { - clientId: process.env.REACT_APP_AAD_APP_CLIENT_ID, - authority: process.env.REACT_APP_AAD_APP_AUTHORITY, - redirectUri: window.location.origin, - }, -}; -const msalClient = new PublicClientApplication(msalConfig); +const msalClient = getMsalConfig(); const App: React.FC = () => { return ( diff --git a/ui/src/utils/utils.tsx b/ui/src/utils/utils.tsx new file mode 100644 index 000000000..8ae041e27 --- /dev/null +++ b/ui/src/utils/utils.tsx @@ -0,0 +1,12 @@ +import { Configuration, PublicClientApplication } from "@azure/msal-browser"; + +export const getMsalConfig = () => { + const msalConfig: Configuration = { + auth: { + clientId: process.env.REACT_APP_AAD_APP_CLIENT_ID, + authority: process.env.REACT_APP_AAD_APP_AUTHORITY, + redirectUri: window.location.origin, + }, + }; + return new PublicClientApplication(msalConfig); +} \ No newline at end of file From b903515403bca4af5529882f721edfa9997d7c28 Mon Sep 17 00:00:00 2001 From: Yuqing Wei Date: Thu, 9 Jun 2022 19:18:21 +0800 Subject: [PATCH 2/4] use id token instead of access token --- ui/src/api/api.tsx | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/ui/src/api/api.tsx b/ui/src/api/api.tsx index 98826f34e..6d802671f 100644 --- a/ui/src/api/api.tsx +++ b/ui/src/api/api.tsx @@ -9,7 +9,7 @@ const MOCK_TOKEN = "mockAppServiceKey"; const msalInstance = getMsalConfig(); export const fetchDataSources = async (project: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/projects/${ project }/datasources?code=${ token }`, { headers: {} }) @@ -19,7 +19,7 @@ export const fetchDataSources = async (project: string) => { }; export const fetchProjects = async () => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return Axios .get<[]>(`${ API_ENDPOINT }/projects?code=${ token }`, { @@ -31,7 +31,7 @@ export const fetchProjects = async () => { }; export const fetchFeatures = async (project: string, page: number, limit: number, keyword: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/projects/${ project }/features?code=${ token }`, { @@ -44,7 +44,7 @@ export const fetchFeatures = async (project: string, page: number, limit: number }; export const fetchFeature = async (project: string, featureId: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/features/${ featureId }?code=${ token }`, {}) .then((response) => { @@ -53,7 +53,7 @@ export const fetchFeature = async (project: string, featureId: string) => { }; export const fetchProjectLineages = async (project: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/projects/${ project }?code=${ token }`, {}) .then((response) => { @@ -62,7 +62,7 @@ export const fetchProjectLineages = async (project: string) => { }; export const fetchFeatureLineages = async (project: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return Axios .get(`${ API_ENDPOINT }/features/lineage/${ project }?code=${ token }`, {}) .then((response) => { @@ -72,7 +72,7 @@ export const fetchFeatureLineages = async (project: string) => { // Following are place-holder code export const createFeature = async (feature: Feature) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return Axios .post(`${ API_ENDPOINT }/features?code=${ token }`, feature, { @@ -86,7 +86,7 @@ export const createFeature = async (feature: Feature) => { } export const updateFeature = async (feature: Feature, id: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); feature.guid = id; return await Axios.put(`${ API_ENDPOINT }/features/${ id }?code=${ token }`, feature, { @@ -100,7 +100,7 @@ export const updateFeature = async (feature: Feature, id: string) => { }; export const deleteFeature = async (qualifiedName: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return await Axios .delete(`${ API_ENDPOINT }/features/${ qualifiedName }?code=${ token }`, { @@ -119,7 +119,7 @@ export const listUserRole = async () => { }; export const getUserRole = async (userName: string) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return await Axios .get(`${ API_ENDPOINT }/user/${userName}/userroles?code=${ token }`, {}) .then((response) => { @@ -128,7 +128,7 @@ export const getUserRole = async (userName: string) => { } export const addUserRole = async (role: Role) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return await Axios .post(`${ API_ENDPOINT }/user/${role.userName}/userroles/new?code=${ token }`, role, { @@ -142,7 +142,7 @@ export const addUserRole = async (role: Role) => { } export const deleteUserRole = async (role: Role) => { - const token = await getAccessToken(msalInstance); + const token = await getIdToken(msalInstance); return await Axios .post(`${ API_ENDPOINT }/user/${role.userName}/userroles/delete?code=${ token }`, role, { @@ -155,7 +155,7 @@ export const deleteUserRole = async (role: Role) => { }); } -export const getAccessToken = async( msalInstance: PublicClientApplication ): Promise => { +export const getIdToken = async( msalInstance: PublicClientApplication ): Promise => { const activeAccount = msalInstance.getActiveAccount(); // This will only return a non-null value if you have logic somewhere else that calls the setActiveAccount API const accounts = msalInstance.getAllAccounts(); const request = { @@ -163,11 +163,12 @@ export const getAccessToken = async( msalInstance: PublicClientApplication ): Pr account: activeAccount || accounts[0] }; await msalInstance.acquireTokenSilent(request).then(response => { - return response.accessToken + console.log(response.idToken) + return response.idToken }).catch(error => { if (error instanceof InteractionRequiredAuthError) { msalInstance.acquireTokenPopup(request).then(response => { - return response.accessToken + return response.idToken }); } }) From de437a25a33f4b68ee23ab3073fa6346867e543e Mon Sep 17 00:00:00 2001 From: Yuqing Wei Date: Thu, 9 Jun 2022 20:40:59 +0800 Subject: [PATCH 3/4] remove mock token --- ui/src/api/api.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/api/api.tsx b/ui/src/api/api.tsx index 6d802671f..bf94df5b8 100644 --- a/ui/src/api/api.tsx +++ b/ui/src/api/api.tsx @@ -5,7 +5,6 @@ import mockUserRole from "./mock/userrole.json"; import { getMsalConfig } from "../utils/utils"; const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT + "/api/v1"; -const MOCK_TOKEN = "mockAppServiceKey"; const msalInstance = getMsalConfig(); export const fetchDataSources = async (project: string) => { @@ -172,5 +171,5 @@ export const getIdToken = async( msalInstance: PublicClientApplication ): Promis }); } }) - return MOCK_TOKEN + return "" } \ No newline at end of file From 937979c43bc3ff2c1d372db0e5e5184b93dd0734 Mon Sep 17 00:00:00 2001 From: Yuqing Wei Date: Thu, 9 Jun 2022 22:24:55 +0800 Subject: [PATCH 4/4] add comment and remove log --- ui/src/api/api.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/api/api.tsx b/ui/src/api/api.tsx index bf94df5b8..ee509411f 100644 --- a/ui/src/api/api.tsx +++ b/ui/src/api/api.tsx @@ -161,8 +161,8 @@ export const getIdToken = async( msalInstance: PublicClientApplication ): Promis scopes: ["User.Read"], account: activeAccount || accounts[0] }; + // Silently acquire an token for a given set of scopes. Will use cached token if available, otherwise will attempt to acquire a new token from the network via refresh token. await msalInstance.acquireTokenSilent(request).then(response => { - console.log(response.idToken) return response.idToken }).catch(error => { if (error instanceof InteractionRequiredAuthError) {