How to get Blazor WASM Office (Outlook) add-in authentication working

43 Views Asked by At

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"]);
        }
    }
}
0

There are 0 best solutions below