UX-AUTH-AAD

This article will explain the code within the UX-AUTH-AAD authentication project. It is a self-contained project that enables a consistent active directory authentication experience across multiple platforms. trellispark specific code in this project is an Index page, the main layout, and the imports.razor required to support them. Additionally, there is the code to support a blazor implementation of active directory authentication. To emulate our implementation, follow this guide.

MainLayout

The main layout is used to set up the notifications for the application. At the top it adds the telerik notification component and sets the position to the center top. In the middle, it adds the cascading notification parameter. At the bottom, it creates the notification class.

@inherits LayoutComponentBase

<TelerikNotification @ref="@Notification.Instance"
                     HorizontalPosition="@NotificationHorizontalPosition.Center"
                     VerticalPosition="@NotificationVerticalPosition.Top"
                     Class="popup-notification">
</TelerikNotification>

<CascadingValue IsFixed="true" Value="@Notification">
    <TelerikRootComponent>
        <div class="page">

            <div class="main">
                <div class="top-row px-4 auth">
                    @if(Configuration["Mode"] == "B2B" || Configuration["Mode"] == "B2C" )
                    {
                        <LoginDisplay />
                    }
                </div>

                <div class="content px-4">
                    @Body
                </div>
            </div>
        </div>
    </TelerikRootComponent>
</CascadingValue>

<style>
    .popup-notification
    {
        z-index: 9999;
    }
</style>


@code{
    Notification Notification { get; set; } = new Notification();
}

Index page

The index page is the landing page of the application. It displays performs deconstruction and interpretation of the URL Parameters and authenticates the user.

First, it creates the page route which accepts the URLParams as a nullable parameter and injects the URLParameterInformation HTTPClientService. For documentation of that service, click here. It also injects an AuthenticationStateProvider. Azure Active Directory authentication also requires the given @using statements.

@page "/{URLParams?}"
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject HTTPClientService<URLParameterInformation, URLParameterInformation> URLParameterClient

@using System.Security.Claims
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

The main display of the page is a table that divides the page in half. On the left is the Authentication page content, defined by the server owner. On the right is the link to the Azure Active Directory authentication mechanism.

<h1>@Configuration["HeaderText"]</h1>

<AuthorizeView>
    <Authorized>
    </Authorized>
    <NotAuthorized>
        <table class="center" border=0 cellspacing=5 cellpadding=0 width=80%>
            <tr>
                <td width="50%">
                    @((MarkupString)AppState.URLParameters.AuthenticationPageContent)
                </td>
                <td width="50%">
                    <table width="100%">
                        <tr>
                            <td>
                                <img src="https://greatideaz.com/wp-content/uploads/2021/09/Great-Ideaz-Logo_Rev1_no_tag.png" alt="Great Ideaz" style="width:40%">
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <h1>Please sign-in or sign-up a new account</h1>
                            </td>
                        </tr>

                        <tr>
                            <td style='padding:.75pt .75pt .75pt .75pt'>
                                <p style='margin-bottom:0cm;line-height:normal'>
                                    By selecting <b>Sign-in</b>,<br>
                                    you agree to the greatideaz<br>
                                    <a href="https://greatideaz.com/termsofservice" target="_blank"><b>Terms of Service</b></a> and <a href="https://www.greatideaz.com/privacypolicy" target="_blank"><b>Privacy Policy</b></a>
                                </p>
                            </td>
                        </tr>
                        <br>
                        <tr>
                            <td>
                                <br>
                                <p>
                                    <a style="font-family: Century Gothic, sans-serif; background-color: #50B748; border-radius: 12px; color: white;  padding: 14px 25px;  text-align: center;  text-decoration: none;  display: inline-block;"
                                       href="@Configuration["SignInURL"]">Sign-in or Sign-up</a>
                                </p>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <p style="font-size: 16px;">
                                    <a href="https://greatideaz.com/contact-us" target="_blank">Contact Us</a>
                                </p>
                            </td>
                        </tr>
                    </table>
                </td>
            </tr>
        </table>
    </NotAuthorized>
</AuthorizeView>

The code section begins with the acceptance of the URL Parameters and the authentication state.

    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    [Parameter]
    public string URLParams { get; set; } = "";

    
   

}

In the OnInitialized, the default theme is applied and the URL parameters are checked for a null value. If they begin with SIGNUP, Signup is removed from the parameters. It is required for standalone authentication only. The parameters are then stored in a URLParameterInformation object and passed to the API for interpretation. Once they are returned to the application, the result is checked and the appropriate action taken.

protected override async Task OnInitializedAsync()
    {
        try
        {
            // Apply the default theme styles.
            await JSRuntime.InvokeVoidAsync("jsInteropHelper.updateLinkStylesheet", "TelerikThemeLink", Configuration["DefaultThemeTelerikURL"]);
            await JSRuntime.InvokeVoidAsync("jsInteropHelper.updateLinkStylesheet", "AppThemeLink", Configuration["DefaultThemeAppURL"]);
            await JSRuntime.InvokeVoidAsync("jsInteropHelper.updateLinkStylesheet", "FontThemeLink", Configuration["DefaultThemeFontURL"]);

            if (URLParams is null)
            {
                AppState.URLParameters.URLParameters = "";
            }
            else
            {
                if (URLParams.Left(6).ToUpper() == "SIGNUP")
                {
                    // In Standalone, SIGNUP is used to display the SignUp component immediately. In AAD, we just need to remove the SIGNUP from the parameters string and not change the display model.
                    URLParams = URLParams.Substring(6, URLParams.Length - 6);
                }

                // We have URL parameters. They are stored in the AppState because the AAD authentication mechanism removes parameters from the URL.
                AppState.URLParameters.URLParameters = URLParams;
            }

            AppState.URLParameters = await URLParameterClient.CallAPI(AppState.URLParameters, "/URLParameters/CallAPI");

            switch (AppState.URLParameters.ActionName)
            {
                case "Join":
                case "JoinExistingPortal":
                case "JoinWorkspaceOwnerAdmin":
                case "JoinServerOwnerAdmin":
                    AppState.UserJoining = true;
                    break;

                default:
                    break;
            }

Finally, if the user is authenticated, then the application passes through to the required page. Otherwise, it refreshes the configuration parameters for the server that the user is signing in to.

if (AppState.User.IsAuthenticated)
            {
                NavigationManager.NavigateTo("Workspaces");
            }
            else
            {
                await AppState.SetServerConfiguration(UserClient);
                this.StateHasChanged();
            }
            await base.OnInitializedAsync();

In the OnParametersSetAsync, the application checks the authentication state for a ClaimPrincipal object. If one is supplied and the ClaimPrincipal is authenticated and the current user is not authenticated, the ClaimPrincipal is deconstructed and it’s values provided to the AppState. trellispark requires the oid (Object IDentifier), preferred_username, and name claims. If the profile name has been provided, the application will sign in and navigate to the workspaces page.

 protected override async Task OnParametersSetAsync()
    {
        try
        {
            ClaimsPrincipal user = (await authenticationStateTask).User;

            if (user.Identity.IsAuthenticated)
            {
                if (!AppState.User.IsAuthenticated)
                {
                    AppState.User = new();
                    AppState.User = new();
                    foreach (Claim claim in user.Claims)
                    {
                        if (claim.Value is not null)
                        {
                            switch (claim.Type)
                            {
                                case "oid":
                                    AppState.User.UserProfileGUID = Guid.Parse(claim.Value);
                                    break;
                                case "preferred_username":
                                    AppState.User.UserProfileName = claim.Value;
                                    break;
                                case "emails":
                                    AppState.User.UserProfileName = claim.Value.Replace("[\"", "").Replace("\"]", "");
                                    break;
                                case "name":
                                    AppState.User.FirstName = claim.Value;
                                    break;
                                case "family_name":
                                    AppState.User.LastName = claim.Value;
                                    break;
                            }
                        }
                    }
                    if ((AppState.User.UserProfileName != "" || AppState.User.FirstName != "") && AppState.User.UserProfileGUID != Guid.Empty)
                    {
                        if (AppState.User.UserProfileName == "")
                        {
                            AppState.User.UserProfileName = AppState.User.FirstName;
                        }
                        else if(AppState.User.FirstName == "")
                        {
                            AppState.User.FirstName = AppState.User.UserProfileName;
                        }

                        AppState.User.Password = "UserProfileGUID";
                        AppState.User.CommandName = "SignIn";
                        AppState.User = await UserClient.CallAPI(AppState.User, "/UserState/CallAPI");

                        if(AppState.User.ErrorMessage == "")
                        {
                            if (AppState.User.Workspaces == "<NewDataSet />" && AppState.URLParameters.ActionName == "No Action" && AppState.URLParameters.InviteUserGUID != Guid.Empty.ToString())
                            {
                                AppState.UserJoining = true;
                                AppState.URLParameters.ActionName = "JoinDefault";
                            }
                            NavigationManager.NavigateTo("Workspaces");
                        }
                        else
                        {
                            Notification.ShowDefaultErrorNotification("Failed to Sign In");
                            await EventLog.Log(1, "AAD SignIn - API Error", AppState.User.ErrorMessage);
                        }
                    }
                    
                }
            }
            await base.OnParametersSetAsync();
        }
        catch (Exception ex)
        {
            await EventLog.Log(1, "AAD b@b SignIn - Unknown", ex.ToString());
        }
    }

Authentication pages

Authentication.razor

The authentication razor page displays the RemoteAuthenticatorView blazor component which contains the Azure Active Directory Authenticator.

LoginDisplay.razor

The LoginDisplay blazor component contains the authorized view and unauthorized views based on the user authentication state. It also contains the code to log out the user from the active directory.

RedirectToLogin.razor

The RedirectToLogin blazor component loads the Authentication page with the return url of the base page.

Updated on April 17, 2023

Was this article helpful?

Related Articles

Need Support?
Can’t find the answer you’re looking for? Don’t worry we’re here to help!
Contact Support

Leave a Comment