Active Directory Federation Services (ADFS) Authentication Adapter
https://github.com/Microsoft/adfsAuthAdapters/tree/master/UsernamePasswordSecondFactor
Microsoft has provided source code to allow a Username and Password to be used as a second factor of authentication in ADFS. You may ask, why would you want to have the usernam and password as a second factor?
- Some automated hacking attempts will can lock out your accounts and/or identify a username and password and obtain access through ADFS or another non-authenticated ADFS system. You can setup a different primary authentication like DUO and then username/password seconds.
- You may want to allow Windows Integrated Authentication (WIA) for internal connections but require certain relying parties to still require a password for access. Think payroll/financial applications.
The second reason is precisely why I initially was interested, however once it was compiled and deployed to ADFS, clicking the sign in button would not work but hitting the enter key would. After some diagnosing I determined that I was getting Javascript errors on the page referencing InputUtil. I remembered that years ago I had adjusted the onload.js Javascript for the login page of ADFS so that it did not require the domain name within the username text box. https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/advanced-customization-of-ad-fs-sign-in-pages. This Javascript function was still being called during the second factor authentication and causing the Javascript in the authentication adapter to not run.
To fix this, there are two things that need to be adjusted. Open the file StringResources.resx within the Visual Studio project and go to the key AuthPage. Add the following Javascript below, above function Login(), compile and redeploy to ADFS.
function LoginErrors(){
this.userNameFormatError = 'Enter your user ID in the format
\u0026quot;domain\user\u0026quot;
or \u0026quot;user@domain\u0026quot;.';
this.passwordEmpty = 'Enter your password.';
this.passwordTooLong = 'Password must be shorter than 128 characters.';
};
var maxPasswordLength = 128;
function InputUtil(errTextElementID, errDisplayElementID) {
if (!errTextElementID) errTextElementID = 'errorText';
if (!errDisplayElementID) errDisplayElementID = 'error';
this.hasFocus = false;
this.errLabel = document.getElementById(errTextElementID);
this.errDisplay = document.getElementById(errDisplayElementID);
};
InputUtil.prototype.canDisplayError = function () {
return this.errLabel && this.errDisplay;
}
InputUtil.prototype.checkError = function () {
if (!this.canDisplayError){
throw new Error ('Error element not present');
}
if (this.errLabel && this.errLabel.innerHTML) {
this.errDisplay.style.display = '';
var cause = this.errLabel.getAttribute('for');
if (cause) {
var causeNode = document.getElementById(cause);
if (causeNode && causeNode.value) {
causeNode.focus();
this.hasFocus = true;
}
}
}
else {
this.errDisplay.style.display = 'none';
}
};
InputUtil.prototype.setInitialFocus = function (input) {
if (this.hasFocus) return;
var node = document.getElementById(input);
if (node) {
if ((/^\s*$/).test(node.value)) {
node.focus();
this.hasFocus = true;
}
}
};
InputUtil.prototype.setError = function (input, errorMsg) {
if (!this.canDisplayError) {
throw new Error('Error element not present');
}
input.focus();
if (errorMsg) {
this.errLabel.innerHTML = errorMsg;
}
this.errLabel.setAttribute('for', input.id);
this.errDisplay.style.display = '';
};
InputUtil.makePlaceholder = function (input) {
var ua = navigator.userAgent;
if (ua != null &&
(ua.match(/MSIE 9.0/) != null ||
ua.match(/MSIE 8.0/) != null ||
ua.match(/MSIE 7.0/) != null)) {
var node = document.getElementById(input);
if (node) {
var placeholder = node.getAttribute("placeholder");
if (placeholder != null && placeholder != '') {
var label = document.createElement('input');
label.type = "text";
label.value = placeholder;
label.readOnly = true;
label.style.position = 'absolute';
label.style.borderColor = 'transparent';
label.className = node.className + ' hint';
label.tabIndex = -1;
label.onfocus = function () { this.nextSibling.focus(); };
node.style.position = 'relative';
node.parentNode.style.position = 'relative';
node.parentNode.insertBefore(label, node);
node.onkeyup = function () { InputUtil.showHint(this); };
node.onblur = function () { InputUtil.showHint(this); };
node.style.background = 'transparent';
node.setAttribute("placeholder", "");
InputUtil.showHint(node);
}
}
}
};
InputUtil.focus = function (inputField) {
var node = document.getElementById(inputField);
if (node) node.focus();
};
InputUtil.hasClass = function(node, clsName) {
return node.className.match(new RegExp('(\s|^)' + clsName + '(\s|$)'));
};
InputUtil.addClass = function(node, clsName) {
if (!this.hasClass(node, clsName)) node.className += " " + clsName;
};
InputUtil.removeClass = function(node, clsName) {
if (this.hasClass(node, clsName)) {
var reg = new RegExp('(\s|^)' + clsName + '(\s|$)');
node.className = node.className.replace(reg, ' ');
}
};
InputUtil.showHint = function (node, gotFocus) {
if (node.value && node.value != '') {
node.previousSibling.style.display = 'none';
}
else {
node.previousSibling.style.display = '';
}
};
You’ll also need to adjust the onload.js Javascript you imported into your ADFS theme. Change function if(Login) with the following.
if(Login) {
Login.submitLoginRequest = function () {
//New Line - This checks to see if the user name text box is visible or not
if(document.getElementById(Login.userNameInput) != null){
var u = new InputUtil();
var e = new LoginErrors();
var userName = document.getElementById(Login.userNameInput);
var password = document.getElementById(Login.passwordInput);
if (userName.value && !userName.value.match('[@\\]'))
{
var userNameValue = 'lorettosystem\' + userName.value;
document.forms['loginForm'].UserName.value = userNameValue;
}
if (!userName.value) {
u.setError(userName, e.userNameFormatError);
return false;
}
if (!password.value)
{
u.setError(password, e.passwordEmpty);
return false;
}
document.forms['loginForm'].submit();
return false;
}else{
document.forms['loginForm'].submit();
return false;
}
};
}