I have a Blazor Office Add-in and want to add MS EntraID Authentication (MS Identity Platform) using the Blazor WebAssembly Authentication library.
I am able to open an Office popup and authenticate, but I am unable send the authenticated "state" back to the main add-in in a non-hacky way.
I was able to hack together a working solution, but I'm pretty sure it isn't the correct way to do it. What is the recommended way to do this?
I took the existing application, and added the Authentication bits that are included when you create a new Blazor app with Microsoft Identity authentication. Instead of the main application navigating to the login route, I open a popup that has a login button.
That popup will then have a login button that goes through the usual app flow. When logon succeeds, it will read the sessionStorage and send the authentication information to the parent. I don't think this is the recommended way, so how should I do it instead?
Main index.razor:
<AuthorizeView>
<NotAuthorized>
<MudButton OnClick="OpenAuthPopup" Style="width:100%">
Sign in
</MudButton>
</NotAuthorized>
<Authorized>
content goes here
</AuthorizeView>
index.razor.cs:
public async Task OpenAuthPopup()
{
await JSModule.InvokeVoidAsync("openAuthPopup");
}
index.razor.js:
export async function openAuthPopup() {
Office.context.ui.displayDialogAsync(new URL("AuthenticationPopup", document.baseURI).href, { height: 50, width: 50 },
(asyncResult) => {
const dialog = asyncResult.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, async (arg) => {
dialog.close();
let tokenResult = JSON.parse(arg.message);
let se = tokenResult.sessionEntries;
for (let key in se) {
sessionStorage.setItem(key, se[key]);
}
location.reload();
});
}
);
}
AuthenticationPopup.razor (in popup):
@layout MainLayout;
@page "/AuthenticationPopup"
<h3>AuthenticationPopup</h3>
<AuthorizeView>
<NotAuthorized>
<MudButton Href="authentication/login" Style="width:100%">
Sign in
</MudButton>
</NotAuthorized>
<Authorized>
Is Authed
</Authorized>
</AuthorizeView>
@code {
}
Authentication.razor (in popup) The with the session info copied, the token is probably not necessary:
@page "/authentication/{action}"
<RemoteAuthenticatorView Action="@Action" OnLogInSucceeded="ReturnKeyToParent" />
<script type="text/javascript">
function returnToParent(token) {
var sessionEntries = getAllLocalStorageEntries();
var jsonToken = JSON.stringify(token);
Office.context.ui.messageParent(JSON.stringify({
isError: false,
accessToken: token,
sessionEntries
}));
}
function getAllLocalStorageEntries() {
var sessionKvPs = Object
.keys(sessionStorage)
.filter(k => k.indexOf('-login.microsoftonline.com-') !== -1)
.map(k => ({ [k]: sessionStorage[k] }));
return Object.assign(...sessionKvPs);
}
function returnError(error) {
console.error(error);
Office.context.ui.messageParent(JSON.stringify({
isError: true,
error
}));
}
</script>
@code {
[Parameter] public string Action { get; set; }
public async Task ReturnKeyToParent()
{
var accessTokenResult = await tokenProvider.RequestAccessToken();
if (accessTokenResult.TryGetToken(out var token))
{
await JSRuntime.InvokeVoidAsync("returnToParent", [token]);
}
else
{
await JSRuntime.InvokeVoidAsync("returnError", ["Unable to retrieve token"]);
}
}
}