-
Notifications
You must be signed in to change notification settings - Fork 3
Refining the authentication dialog
This tutorial will guide you through the process of customizing the authentication dialog that we created in the last tutorial. Specifically, it will demonstrate how to use actions and conditions to enable or disable dialog controls and how to use user interface tasks to signal that authentication is in process.
Requirements
In order to complete this tutorial, you will need the following:
- A web-server to host your content files
- An operational InCert server
- An xml or text editor
Setup
This tutorial assumes that you have created the tutorial, Creating Remote Content And Authenticating Users, and have a version of the engine that will start and attempt to contact your web-server.
Where we left off
In the last tutorial, we created an authentication dialog, but it was missing several key features:
- Users should not be able to click the dialog's "Login" button without providing credentials.
- Users should not be able to interact with the dialog while authentication is in progress.
- The dialog should signal that authentication is in progress.
Enabling and Disabling the "Login" button if fields are empty
- The first thing that we need to do is to disable the authentication banner's "Login" button if one of its fields is empty. To do this, update banners.xml to the following:
<Content xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://incert.incommon.org/schemas ../Schemas/tasklist.xsd">
<Banners>
<SimpleBanner name="LoginBanner" height="550" width="500">
<Content>
<SimpleParagraph margin="0,36,0,36" fontSize="24" alignment="Center">
<Content>
<DirectTextContent>!ApplicationTitle! Login</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleParagraph>
<Content>
<DirectTextContent>Username:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="username" controlKey="username"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Passphrase:</DirectTextContent>
</Content>
</SimpleParagraph>
<PasswordInputField settingKey="passphrase" controlKey="passphrase"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 2:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential2"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 3:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential3"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 4:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential4"/>
<SimpleParagraph controlKey="Instructions" margin="0,12,0,0">
<Content>
<DirectTextContent>Please provide your network credentials and click Login to continue.</DirectTextContent>
</Content>
</SimpleParagraph>
</Content>
<Buttons>
<ResultButton>
<Target>NextButton</Target>
<Text>Login</Text>
<IsDefaultButton>true</IsDefaultButton>
<Result>ControlResults.NextResult</Result>
</ResultButton>
<DisabledButton>
<Target>BackButton</Target>
<Text>Back</Text>
</DisabledButton>
<UrlButton>
<Target>HelpButton</Target>
<Text>Help</Text>
<Value>https://certdev0.incommontest.org/incommon/index.html</Value>
</UrlButton>
</Buttons>
<Actions>
<DisableControlAction>
<ControlKey>NextButton</ControlKey>
</DisableControlAction>
</Actions>
</SimpleBanner>
</Banners>
</Content>
Here, we've added an action to disable our banner's "Login" button. This action will fire when the banner is loaded and whenever a change is made to one of the banner's elements.
- Upload banners.xml to your web-server and run your version of the engine. The authentication dialog's "Login" button should now be disabled.
- This is not quite the behavior we want. We only want the "Login" button to be disabled if one or more of the banner's field are empty. To do this, we need to add some conditions to our action:
<DisableControlAction>
<Conditions.Any>
<Settings.SettingNotPresent key="username" />
<Settings.SettingNotPresent key="passphrase" />
<Settings.SettingNotPresent key="credential2" />
<Settings.SettingNotPresent key="credential3" />
<Settings.SettingNotPresent key="credential4" />
</Conditions.Any>
<ControlKey>NextButton</ControlKey>
</DisableControlAction>
You can use conditions to determine whether tasks or actions execute. There are two important things to note about the above:
- We are using five instances of the
Settings.SettingNotPresent
condition. Each instance will return true if the condition is satisfied (which is to say, that there is no entry in the engine's setting store for the provided key) or false otherwise. - Our conditions are children of a 'Conditions.Any' tag. This means that the action will fire if any of the conditions is true. The ultimate effect will be that our banner's "Login" button will be disabled in any of its fields are blank.
- We also need to include code to re-enable the "Login" button. To do this, add the following action block to our banner:
<EnableControlAction>
<Conditions.All>
<Settings.SettingPresent key="username" />
<Settings.SettingPresent key="passphrase" />
<Settings.SettingPresent key="credential2" />
<Settings.SettingPresent key="credential3" />
<Settings.SettingPresent key="credential4" />
</Conditions.All>
<ControlKey>NextButton</ControlKey>
</EnableControlAction>
This action block is essentially the opposite of the above, employing five Settings.SettingPresent
conditions wrapped in a Conditions.All
parent. The end result is that the banner's "Login" button will be enabled if all five of its banner fields have content.
Your banners.xml file should now be as follows:
<Content xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://incert.incommon.org/schemas ../Schemas/tasklist.xsd">
<Banners>
<SimpleBanner name="LoginBanner" height="550" width="500">
<Content>
<SimpleParagraph margin="0,36,0,36" fontSize="24" alignment="Center">
<Content>
<DirectTextContent>!ApplicationTitle! Login</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleParagraph>
<Content>
<DirectTextContent>Username:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="username" controlKey="username"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Passphrase:</DirectTextContent>
</Content>
</SimpleParagraph>
<PasswordInputField settingKey="passphrase" controlKey="passphrase"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 2:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential2"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 3:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential3"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 4:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential4"/>
<SimpleParagraph controlKey="Instructions" margin="0,12,0,0">
<Content>
<DirectTextContent>Please provide your network credentials and click Login to continue.</DirectTextContent>
</Content>
</SimpleParagraph>
</Content>
<Buttons>
<ResultButton>
<Target>NextButton</Target>
<Text>Login</Text>
<IsDefaultButton>true</IsDefaultButton>
<Result>ControlResults.NextResult</Result>
</ResultButton>
<DisabledButton>
<Target>BackButton</Target>
<Text>Back</Text>
</DisabledButton>
<UrlButton>
<Target>HelpButton</Target>
<Text>Help</Text>
<Value>https://certdev0.incommontest.org/incommon/index.html</Value>
</UrlButton>
</Buttons>
<Actions>
<DisableControlAction>
<Conditions.Any>
<Settings.SettingNotPresent key="username" />
<Settings.SettingNotPresent key="passphrase" />
<Settings.SettingNotPresent key="credential2" />
<Settings.SettingNotPresent key="credential3" />
<Settings.SettingNotPresent key="credential4" />
</Conditions.Any>
<ControlKey>NextButton</ControlKey>
</DisableControlAction>
<EnableControlAction>
<Conditions.All>
<Settings.SettingPresent key="username" />
<Settings.SettingPresent key="passphrase" />
<Settings.SettingPresent key="credential2" />
<Settings.SettingPresent key="credential3" />
<Settings.SettingPresent key="credential4" />
</Conditions.All>
<ControlKey>NextButton</ControlKey>
</EnableControlAction>
</Actions>
</SimpleBanner>
</Banners>
</Content>
- Upload banners.xml to your web-server and run the engine. The authentication dialog's "Login" button should now be disabled if one or more of its fields is empty.
Informing users that authentication is in process
Now that we have the "Login" button working properly, we need to inform users that authentication is in process after they click the "Login" button. To do this, we will use a combination of control actions and tasks.
There are a lot of ways we can handle the authentication process. For now, let's add a progress message to our dialog and show this message while authentication is pending while disabling the dialog's other controls
-
Add this
ProgressParagraph
block to your login banner's content block:
When active, the ProgressParagraph
will append a series of dots to its base text. Note that the ProgressParagraph
has both settingKey
and controlKey
attributes. These values as used to enable or disable the paragraph and change its content dynamically.
- We don't want our
ProgressParagraph
to be visible by default. Add thisHideControlAction
action to our banner'sActions
block:
<HideControlAction onetime="true">
<ControlKey>AuthenticatingMessage</ControlKey>
</HideControlAction>
The ControlKey
value tells the engine to perform the action on our ProgressParagraph
. We're using the onetime="true"
attribute to tell the engine only to perform the action once. We only need to hide the control when the banner is initialized. There are no conditions here, so this action will always fire, but only once when the banner is initialized.
- Your banners.xml file should now be as follows:
<Content xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://incert.incommon.org/schemas ../Schemas/tasklist.xsd">
<Banners>
<SimpleBanner name="LoginBanner" height="550" width="500">
<Content>
<SimpleParagraph margin="0,36,0,36" fontSize="24" alignment="Center">
<Content>
<DirectTextContent>!ApplicationTitle! Login</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleParagraph>
<Content>
<DirectTextContent>Username:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="username" controlKey="username"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Passphrase:</DirectTextContent>
</Content>
</SimpleParagraph>
<PasswordInputField settingKey="passphrase" controlKey="passphrase"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 2:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential2"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 3:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential3"/>
<SimpleParagraph margin="0,8,0,0">
<Content>
<DirectTextContent>Credential 4:</DirectTextContent>
</Content>
</SimpleParagraph>
<SimpleInputField settingKey="credential4"/>
<SimpleParagraph controlKey="Instructions" margin="0,12,0,0">
<Content>
<DirectTextContent>Please provide your network credentials and click Login to continue.</DirectTextContent>
</Content>
</SimpleParagraph>
<ProgressParagraph settingKey="AuthenticatingMessage" controlKey="AuthenticatingMessage" margin="0,12,0,0">
<Content>
<DirectTextContent>Please wait while !ApplicationTitle! verifies your network credentials</DirectTextContent>
</Content>
</ProgressParagraph>
</Content>
<Buttons>
<ResultButton>
<Target>NextButton</Target>
<Text>Login</Text>
<IsDefaultButton>true</IsDefaultButton>
<Result>ControlResults.NextResult</Result>
</ResultButton>
<DisabledButton>
<Target>BackButton</Target>
<Text>Back</Text>
</DisabledButton>
<UrlButton>
<Target>HelpButton</Target>
<Text>Help</Text>
<Value>https://certdev0.incommontest.org/incommon/index.html</Value>
</UrlButton>
</Buttons>
<Actions>
<DisableControlAction>
<Conditions.Any>
<Settings.SettingNotPresent key="username" />
<Settings.SettingNotPresent key="passphrase" />
<Settings.SettingNotPresent key="credential2" />
<Settings.SettingNotPresent key="credential3" />
<Settings.SettingNotPresent key="credential4" />
</Conditions.Any>
<ControlKey>NextButton</ControlKey>
</DisableControlAction>
<EnableControlAction>
<Conditions.All>
<Settings.SettingPresent key="username" />
<Settings.SettingPresent key="passphrase" />
<Settings.SettingPresent key="credential2" />
<Settings.SettingPresent key="credential3" />
<Settings.SettingPresent key="credential4" />
</Conditions.All>
<ControlKey>NextButton</ControlKey>
</EnableControlAction>
<HideControlAction onetime="true">
<ControlKey>AuthenticatingMessage</ControlKey>
</HideControlAction>
</Actions>
</SimpleBanner>
</Banners>
</Content>
- Upload banners.xml to your web server and re-run the engine. You should not see the progress paragraph.
- When the user clicks the
Login
button, the authentication dialog returns aNextResult
and the engine executes its next task, which is currentlyAutentication.AuthenticateUser
. In tasklist.xml, add these four tasks between theUserInterface.ShowBorderedBannerModal
and theAutentication.AuthenticateUser
tasks:
<UserInterface.DisableAllBannerDialogControls>
<Properties>
<Dialog>Main dialog</Dialog>
<ExcludeControlKey>HelpButton</ExcludeControlKey>
<ExcludeControlKey>AuthenticatingMessage</ExcludeControlKey>
</Properties>
</UserInterface.DisableAllBannerDialogControls>
<UserInterface.CollapseBannerControl>
<Properties>
<Dialog>Main dialog</Dialog>
<ControlKey>Instructions</ControlKey>
</Properties>
</UserInterface.CollapseBannerControl>
<UserInterface.ShowBannerControl>
<Properties>
<Dialog>Main dialog</Dialog>
<ControlKey>AuthenticatingMessage</ControlKey>
</Properties>
</UserInterface.ShowBannerControl>
<UserInterface.StartMessageTimer>
<Properties>
<SettingKey>AuthenticatingMessage</SettingKey>
</Properties>
</UserInterface.StartMessageTimer>
Here, we are first disabling all of the authentication dialog's controls except the HelpButton
using the UserInterface.DisableAllBannerDialogControls
task. We're also not disabling our AuthenticatingMessage
progress paragraph because we don't want its text to be grayed-out when it becomes visible.
Next, we hide the dialog's Instructions
paragraph using UserInterface.CollapseBannerControl
, which both makes the paragraph invisible and "collapses" the paragraph so that it no longer takes up vertical space on the dialog.
We then use UserInterface.ShowBannerControl
to show our ProgressParagraph
. UserInterface.StartMessageTimer
tells our ProgressParagraph
to start animating.
- Your tasklist.xml file should be as follows:
<Content xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://incert.incommon.org/schemas ../Schemas/tasklist.xsd">
<Branches>
<RoleBranch name="main" role="Remote" roleMode="Normal">
<Control.GetContentFromEndpoint>
<Properties>
<ContentName>banners.xml</ContentName>
</Properties>
</Control.GetContentFromEndpoint>
<UserInterface.StopMessageTimer>
<Properties>
<SettingKey>Splash screen progress text</SettingKey>
</Properties>
</UserInterface.StopMessageTimer>
<UserInterface.HideDialog>
<Properties>
<Dialog>Splash screen dialog</Dialog>
</Properties>
</UserInterface.HideDialog>
<UserInterface.ShowBorderedBannerModal>
<Properties>
<Dialog>Main dialog</Dialog>
<Banner>LoginBanner</Banner>
</Properties>
</UserInterface.ShowBorderedBannerModal>
<UserInterface.DisableAllBannerDialogControls>
<Properties>
<Dialog>Main dialog</Dialog>
<ExcludeControlKey>HelpButton</ExcludeControlKey>
<ExcludeControlKey>AuthenticatingMessage</ExcludeControlKey>
</Properties>
</UserInterface.DisableAllBannerDialogControls>
<UserInterface.CollapseBannerControl>
<Properties>
<Dialog>Main dialog</Dialog>
<ControlKey>Instructions</ControlKey>
</Properties>
</UserInterface.CollapseBannerControl>
<UserInterface.ShowBannerControl>
<Properties>
<Dialog>Main dialog</Dialog>
<ControlKey>AuthenticatingMessage</ControlKey>
</Properties>
</UserInterface.ShowBannerControl>
<UserInterface.StartMessageTimer>
<Properties>
<SettingKey>AuthenticatingMessage</SettingKey>
</Properties>
</UserInterface.StartMessageTimer>
<Authentication.AuthenticateUser>
<Properties>
<UsernameKey>username</UsernameKey>
<PassphraseKey>passphrase</PassphraseKey>
<Credential2Key>credential2</Credential2Key>
<Credential3Key>credential3</Credential3Key>
<Credential4Key>credential4</Credential4Key>
<CertificateProvider>incommontest.org</CertificateProvider>
</Properties>
</Authentication.AuthenticateUser>
</RoleBranch>
</Branches>
</Content>
- Upload tasklist.xml to your web-server and run the engine. The authentication dialog should now disable itself and show our animated progress message when the user clicks "Login."
At this point, things are almost perfect, but if you look closely, you'll notice that the "Login" button gets disabled and then re-enabled. This happens because we have a control action that tells the banner to re-enable the "Login" button if all of its text fields have content. The result is that the "Login" button gets disabled when we tell the dialog (via the UserInterface.DisableAllBannerDialogControls
task) to disable all of its controls, but then the EnableControlAction
fires again and because all of its conditions are met, re-enables the "Login" button.
- To fix this, we need to alter the conditions that determine whether
EnableControlAction
andDisableControlAction
fire:
<DisableControlAction>
<Conditions.Any>
<Settings.SettingNotPresent key="username" />
<Settings.SettingNotPresent key="passphrase" />
<Settings.SettingNotPresent key="credential2" />
<Settings.SettingNotPresent key="credential3" />
<Settings.SettingNotPresent key="credential4" />
<Settings.SettingEquals key="authenticating" value="true" />
</Conditions.Any>
<ControlKey>NextButton</ControlKey>
</DisableControlAction>
<EnableControlAction>
<Conditions.All>
<Settings.SettingPresent key="username" />
<Settings.SettingPresent key="passphrase" />
<Settings.SettingPresent key="credential2" />
<Settings.SettingPresent key="credential3" />
<Settings.SettingPresent key="credential4" />
<Settings.SettingNotEqual key="authenticating" value="true" />
</Conditions.All>
We've now changed the conditions driving each action so that the "Login" button will be disabled if any one of the settings associated with the dialog's fields are empty or if the settings value for the key authenticating
is true. If none of the field values are blank and the authenticating
is false, then we will enable the "Login" button.
- We also will need to introduce a task to set the value of
authenticating
. To do this, add the following task block immediately afterUserInterface.ShowBorderedBannerModal
:
<Settings.SetSettingText>
<Properties>
<Setter key="authenticating">true</Setter>
</Properties>
</Settings.SetSettingText>
This task block will set authenticating
to 'true' in the engine's temporary settings store, and this, in turn, will prevent EnableControlAction
from re-enabling the "Login" button.
- Upload tasklist.xml and banners.xml to your web-server and run the engine again. This time, the "Login" button should stay disabled after you enter your credentials and click "Login."
Conclusion
Our authentication dialog is now a lot better than it was when we started our tutorial. Users cannot click its "Login" button until they have completed all of its fields, and we now inform users that authentication is in progress.
There is still some work to be done, though. If users enter invalid credentials, the engine raises and error dialog and exits. This is a bit draconian. In the next tutorial, we will had error handling to our authentication dialog to give users the option of re-entering their credentials without having to restart the engine.