national-gold
national-gold2y ago

Auzure AD B2C for Authentication and Authorization

Hi All, I am trying to set up auth with Azure AD. After a successful login on Azure AD B2C, I am redirected back to the login page unless I refresh and then I am redirected to the Dashboad page.
15 Replies
Omer
Omer2y ago
Hey @.dark.energy 👋, I am glad to see you ⚡️ Check the auth status in the checkAuth function of the authProvider. If user logged in you can return with "Promise.resolve". There is a similar implementation for supabase https://github.com/refinedev/refine/blob/next/examples/dataProvider/supabase/src/App.tsx#L94
GitHub
refine/App.tsx at next · refinedev/refine
Build your React-based CRUD applications, without constraints. - refine/App.tsx at next · refinedev/refine
national-gold
national-gold2y ago
Thanks @Omer I am doing that but seems like checkAuth doesn't get called after a successful redirect. Msal.js has events that you can hook into where set the activeaccount.
const authProvider: AuthProvider = {
login: () => { return Promise.resolve(false) },
register: () => Promise.resolve(),
resetPassword: () => Promise.resolve(),
updatePassword: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkAuth: () => {
if (isAuthenticated) return Promise.resolve();
try {
if (account) {
// Silently acquires an access token which is then attached to a request for API access
instance.acquireTokenSilent(request).then((response) => {
console.log(response)
localStorage.setItem(TOKEN_KEY, response.accessToken)
}).catch((e) => {
instance.acquireTokenPopup(request).then((response) => {
localStorage.setItem(TOKEN_KEY, response.accessToken)
});
});
return Promise.resolve();
} else {
return Promise.reject();
}
}
catch (error) {
return Promise.reject();
}
},
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve(),
getUserIdentity: async (): Promise<AccountInfo> => {
if (account === null || account === undefined) {
return Promise.reject();
}
return Promise.resolve(account);
},
};
const authProvider: AuthProvider = {
login: () => { return Promise.resolve(false) },
register: () => Promise.resolve(),
resetPassword: () => Promise.resolve(),
updatePassword: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkAuth: () => {
if (isAuthenticated) return Promise.resolve();
try {
if (account) {
// Silently acquires an access token which is then attached to a request for API access
instance.acquireTokenSilent(request).then((response) => {
console.log(response)
localStorage.setItem(TOKEN_KEY, response.accessToken)
}).catch((e) => {
instance.acquireTokenPopup(request).then((response) => {
localStorage.setItem(TOKEN_KEY, response.accessToken)
});
});
return Promise.resolve();
} else {
return Promise.reject();
}
}
catch (error) {
return Promise.reject();
}
},
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve(),
getUserIdentity: async (): Promise<AccountInfo> => {
if (account === null || account === undefined) {
return Promise.reject();
}
return Promise.resolve(account);
},
};
msal callback
const msalInstance = new PublicClientApplication(msalConfig);


msalInstance.addEventCallback(event => {
if (event.eventType === EventType.LOGIN_SUCCESS) {
// alert("Successful Login")
const payload: EventPayload = event.payload;
msalInstance.setActiveAccount(payload as AccountInfo)

let account = msalInstance.getActiveAccount()

const request: SilentRequest = {
...tokenRequest,
account: account!
};

// Silently acquires an access token which is then attached to a request for API access
msalInstance.acquireTokenSilent(request).then((response) => {
console.log("Fetching access token: success")
console.log("Scopes", response.scopes)
console.log("Token Type", response.tokenType)

localStorage.setItem(TOKEN_KEY, response.accessToken)
}).catch((e) => {
msalInstance.acquireTokenPopup(request).then((response) => {
localStorage.setItem(TOKEN_KEY, response.accessToken)
});
});
}

})
const msalInstance = new PublicClientApplication(msalConfig);


msalInstance.addEventCallback(event => {
if (event.eventType === EventType.LOGIN_SUCCESS) {
// alert("Successful Login")
const payload: EventPayload = event.payload;
msalInstance.setActiveAccount(payload as AccountInfo)

let account = msalInstance.getActiveAccount()

const request: SilentRequest = {
...tokenRequest,
account: account!
};

// Silently acquires an access token which is then attached to a request for API access
msalInstance.acquireTokenSilent(request).then((response) => {
console.log("Fetching access token: success")
console.log("Scopes", response.scopes)
console.log("Token Type", response.tokenType)

localStorage.setItem(TOKEN_KEY, response.accessToken)
}).catch((e) => {
msalInstance.acquireTokenPopup(request).then((response) => {
localStorage.setItem(TOKEN_KEY, response.accessToken)
});
});
}

})
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("http://localhost:8000/api/v1", axiosInstance)}
authProvider={authProvider}
LoginPage={LoginPage}
Layout={Layout}
DashboardPage={DashboardPage}
resources={[
{
name: "documents",
list: DocumentList,
},
]}
options={{
disableTelemetry: true
}} />
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("http://localhost:8000/api/v1", axiosInstance)}
authProvider={authProvider}
LoginPage={LoginPage}
Layout={Layout}
DashboardPage={DashboardPage}
resources={[
{
name: "documents",
list: DocumentList,
},
]}
options={{
disableTelemetry: true
}} />
Omer
Omer2y ago
Can you add "console.log" at the top of checkAuth? You can also check our Auth0 example, it works similarly https://github.com/refinedev/refine/blob/next/examples/authProvider/auth0/src/App.tsx#L36 https://refine.dev/docs/examples/auth-provider/auth0/
national-gold
national-gold2y ago
I have followed the Auth0 example.
national-gold
national-gold2y ago
No description
national-gold
national-gold2y ago
Now, if I were to refresh this screen, then I get redirected to Dashboard
Omer
Omer2y ago
Probably returning Promise.reject from "checkAuth" after redirect. You should wait for the result of the "acquireTokenSilent" and "acquireTokenPopup" async functions. Could you try like this
checkAuth: async () => {
if (isAuthenticated) return Promise.resolve();
try {
if (account) {
// Silently acquires an access token which is then attached to a request for API access
try {
const token = await instance.acquireTokenSilent(request)
localStorage.setItem(TOKEN_KEY, response.accessToken)
return Promise.resolve();

}
catch(e) {
return Promise.reject();
}

}
catch (error) {
return Promise.reject();
}
},
checkAuth: async () => {
if (isAuthenticated) return Promise.resolve();
try {
if (account) {
// Silently acquires an access token which is then attached to a request for API access
try {
const token = await instance.acquireTokenSilent(request)
localStorage.setItem(TOKEN_KEY, response.accessToken)
return Promise.resolve();

}
catch(e) {
return Promise.reject();
}

}
catch (error) {
return Promise.reject();
}
},
national-gold
national-gold2y ago
Okay
checkAuth: async () => {
try {
if (account) {
// Silently acquires an access token which is then attached to a request for API access
try {
const token = await instance.acquireTokenSilent(request)
localStorage.setItem(TOKEN_KEY, token.accessToken)
return Promise.resolve();
}
catch (e) {
return Promise.reject();
}
}
else {
return Promise.reject();
}
}
catch (error) {
return Promise.reject();
}
},
checkAuth: async () => {
try {
if (account) {
// Silently acquires an access token which is then attached to a request for API access
try {
const token = await instance.acquireTokenSilent(request)
localStorage.setItem(TOKEN_KEY, token.accessToken)
return Promise.resolve();
}
catch (e) {
return Promise.reject();
}
}
else {
return Promise.reject();
}
}
catch (error) {
return Promise.reject();
}
},
I needed an else in if otherwise it was going straight to Dashboard But this leads to the same behaviour
Omer
Omer2y ago
Could you try this?
checkAuth: async () => {
try {
const token = await instance.acquireTokenSilent(request)
localStorage.setItem(TOKEN_KEY, token.accessToken)
return Promise.resolve();
}
catch (e) {
return Promise.reject();
}
},
checkAuth: async () => {
try {
const token = await instance.acquireTokenSilent(request)
localStorage.setItem(TOKEN_KEY, token.accessToken)
return Promise.resolve();
}
catch (e) {
return Promise.reject();
}
},
if it doesn't work i will try to create an example
national-gold
national-gold2y ago
Not working
Omer
Omer2y ago
Tutorial: Create a React single-page app that uses auth code flow -...
In this tutorial, you create a React SPA that can sign in users and use the auth code flow to obtain an access token from the Microsoft identity platform and call the Microsoft Graph API.
national-gold
national-gold2y ago
import { Configuration, LogLevel } from "@azure/msal-browser";

export const msalConfig: Configuration = {
auth: {
clientId: `${process.env.REACT_APP_AZURE_AAD_CLIENT_ID}`,
authority: `https://${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.b2clogin.com/${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.onmicrosoft.com/${process.env.REACT_APP_AZURE_AAD_POLICY_NAME}`,
knownAuthorities: [`${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.b2clogin.com`],
redirectUri: "http://localhost:3000/",
postLogoutRedirectUri: window.location.origin,
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
},
};

// Add scopes here for ID token to be used at Microsoft identity platform endpoints.
export const loginRequest = {
scopes: ["User.Read"]
};

export const tokenRequest = {
scopes: [
"https://xx.onmicrosoft.com/api/read"
]
};


// Add the endpoints here for Microsoft Graph API services you'd like to use.
export const graphConfig = {
graphMeEndpoint: "Enter_the_Graph_Endpoint_Here/v1.0/me"
};
import { Configuration, LogLevel } from "@azure/msal-browser";

export const msalConfig: Configuration = {
auth: {
clientId: `${process.env.REACT_APP_AZURE_AAD_CLIENT_ID}`,
authority: `https://${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.b2clogin.com/${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.onmicrosoft.com/${process.env.REACT_APP_AZURE_AAD_POLICY_NAME}`,
knownAuthorities: [`${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.b2clogin.com`],
redirectUri: "http://localhost:3000/",
postLogoutRedirectUri: window.location.origin,
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
},
};

// Add scopes here for ID token to be used at Microsoft identity platform endpoints.
export const loginRequest = {
scopes: ["User.Read"]
};

export const tokenRequest = {
scopes: [
"https://xx.onmicrosoft.com/api/read"
]
};


// Add the endpoints here for Microsoft Graph API services you'd like to use.
export const graphConfig = {
graphMeEndpoint: "Enter_the_Graph_Endpoint_Here/v1.0/me"
};
Omer
Omer2y ago
Is it possible to share information with me via private message? I can quickly skip the setup steps. REACT_APP_AZURE_AAD_CLIENT_ID, REACT_APP_AZURE_AAD_TENANT_NAME etc
national-gold
national-gold2y ago
Sure
Omer
Omer2y ago
Thank you! 🎉 Ok I found the problem. Could you try this?
const App: React.FC = () => {
const { instance, accounts, inProgress } = useMsal();

if (inProgress === "login" || inProgress === "handleRedirect") {
return <div>Loading...</div>;
}
const App: React.FC = () => {
const { instance, accounts, inProgress } = useMsal();

if (inProgress === "login" || inProgress === "handleRedirect") {
return <div>Loading...</div>;
}
checkAuth: async () => {
try {
const token = await instance.acquireTokenSilent(request);
localStorage.setItem("TOKEN_KEY", token.accessToken);
return Promise.resolve();
} catch (e) {
console.log("error", { e });
return Promise.reject();
}
},
checkAuth: async () => {
try {
const token = await instance.acquireTokenSilent(request);
localStorage.setItem("TOKEN_KEY", token.accessToken);
return Promise.resolve();
} catch (e) {
console.log("error", { e });
return Promise.reject();
}
},