Tag Archives: SAML

ADFS Username Behavior

Problem

ADFS 4.0 on Windows Server 2016 tells users to log in with their full email address “someone@example.com.”  This generates many support requests, and complaints about too much typing.

Additionally, some extranet users may have email addresses not on the domain, and it’s unclear which email address they should supply.

This affects both the ADFS log in page, and the ADFS password change page.

Solution Methodology

ADFS Server 4.0 has PowerShell cmdlets to manage the content delivered to users during authentication requests: https://technet.microsoft.com/windows-server-docs/identity/ad-fs/operations/ad-fs-user-sign-in-customization

We’ll focus on the following

Get-AdfsWebTheme

and

Set-AdfsWebTheme

Of particular interest here is that we’re able to modify the JavaScript that runs on these pages.

Steps

Use PowerShell to manage custom ADFS Themes

  1. Export the Default ADFS Theme using this snippet:
     Export-ADFSWebTheme -Name "Default" -DirectoryPath c:\test
  2. Use your  favorite editor to open c:\test\script\onload.js
  3. Add the snippets from below (as desired) into onload.js
  4. Create a New ADFS Theme
     New-AdfsWebTheme -Name BetterDefault -SourceName c:\test 
    1. Set your new theme as the default (best for testing)
       Set-ADFSWebConfig -ActiveThemeName BetterDefault 
  5. Alternatively, you may update an existing theme with your code changes
    Set-AdfsWebTheme -TargetName "Default" -AdditionalFileResource @{Uri=“/adfs/portal/script/onload.js”;Path=“C:\theme\script\onload.js"}

Placeholder Text Solution

To update the “someone@example.com” placeholder on both the login and the password change ADFS pages, paste this code into your onload.js, and update your ADFS theme.

function UpdatePlaceholders() {
    var userName;
    if (typeof Login != 'undefined'){
        userName = document.getElementById(Login.userNameInput) 
    }
    if (typeof UpdatePassword != 'undefined'){
        userName = document.getElementById(UpdatePassword.userNameInput);
    }
    if (typeof userName != 'undefined'){
        userName.setAttribute("placeholder","Username");
    }
}

document.addEventListener("DOMContentLoaded", function(){
  // Handler when the DOM is fully loaded
  UpdatePlaceholders()
});

 

Formatting of the Username field

For single-domain organizations, it may be less than desirable to force users to enter the domain name as part of their username. To “fix” this requirement of entering usernames in a format of “domain\username” or “username@domain.com”, paste the following code into your onload.js.  Make sure to update your domain where appropriate.

Logon Username Format Solution

 


if (typeof Login != 'undefined'){
    Login.submitLoginRequest = function () { 
    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 = 'example.org\\' + 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;
};
}

Password Change Username Formatting Solution


if (typeof UpdatePassword != 'undefined'){
    UpdatePassword.submitPasswordChange = function () { 
    var u = new InputUtil();
    var e = new UpdErrors();

    var userName = document.getElementById(UpdatePassword.userNameInput);
    var oldPassword = document.getElementById(UpdatePassword.oldPasswordInput);
    var newPassword = document.getElementById(UpdatePassword.newPasswordInput);
    var confirmNewPassword = document.getElementById(UpdatePassword.confirmNewPasswordInput);

    if (userName.value && !userName.value.match('[@\\\\]')) 
    {
        var userNameValue = 'example.org\\' + userName.value;
        document.forms['updatePasswordForm'].UserName.value = userNameValue;
    }

    if (!userName.value) {
       u.setError(userName, e.userNameFormatError);
       return false;
    }

    if (!oldPassword.value) {
        u.setError(oldPassword, e.oldPasswordEmpty);
        return false;
    }

    if (oldPassword.value.length > maxPasswordLength) {
        u.setError(oldPassword, e.oldPasswordTooLong);
        return false;
    }

    if (!newPassword.value) {
        u.setError(newPassword, e.newPasswordEmpty);
        return false;
    }

    if (!confirmNewPassword.value) {
        u.setError(confirmNewPassword, e.confirmNewPasswordEmpty);
        return false;
    }

    if (newPassword.value.length > maxPasswordLength) {
        u.setError(newPassword, e.newPasswordTooLong);
        return false;
    }

    if (newPassword.value !== confirmNewPassword.value) {
        u.setError(confirmNewPassword, e.mismatchError);
        return false;
    }

    return true;
};
}

Thanks for reading!  If you have any questions, feel free to send me a tweet @crossan007.

ADFS 4.0 on Server 2016 <-> Outlook Web App 2013

I recently enabled SAML authentication on Outlook Web App 2013, following the TechNet Documentation here: https://technet.microsoft.com/en-us/library/dn635116(v=exchg.150).aspx

It seemed to work fine; however, I would occasionally (about 1/8 attempts) receive an error message saying: “WrongAudienceUriOrBadSigningCert”

I had already added my ADFS token signing certificate to the Exchange server’s trusted root store as  suggested here:  https://flamingkeys.com/exchange-2013-with-ad-fs-login-fails-with-wrongaudienceuriorbadsigningcert/

The truly troubling thing was, that the issue could not be reproduced reliably.  It affected both internal and external devices (both the primary ADFS and the ADFS Web Application Proxy servers)

I watched a fiddler trace as I attempted to access OWA, and the only difference between successful and failed attempts was a  “/” at the end of the URL.

This can be observed in the POST body of the 302 to owa:


<saml:AudienceRestrictionCondition>
<saml:Audience>https://mail.corp.org/owa/</saml:Audience>
</saml:AudienceRestrictionCondition>

<saml:AudienceRestrictionCondition>
<saml:Audience>https://mail.corp.org/owa</saml:Audience>
</saml:AudienceRestrictionCondition>

This is the token issued to me by my ADFS4 Server!  It would seem that the tokens issued by the IdP do not contain a consistent Audience tag.

The TechNet documentation states very clearly that

The inclusion of the trailing slash / in the URL examples shown below is intentional. It’s important to ensure that both the AD FS relying party trusts and Exchange Audience URI’s are identical. This means the AD FS relying party trusts and Exchange Audience URI’s should both have or both emit the trailing slashes in their URLs. The examples in this section contain the trailing /’s after any url ending with “owa” ( /owa/) or “ecp” (/ecp/).

Ignoring this advise, I added all 4 urls to my Exchange farm configuration


$uris = @("https://mail.corp.org/owa/","https://mail.corp.org/ecp/","https://mail.corp.org/owa","https://mail.corp.org/ecp")

Set-OrganizationConfig -AdfsIssuer "https://adfs.corp.org/adfs/ls/" -AdfsAudienceUris $uris -AdfsSignCertificateThumbprint "<thhumb>"

Having 4 audience URIs resulted in a 100% success rate while attempting to open OWA from a successful ADFS authentication.

I hope this helps someone, as I couldn’t seem to find this issue anywhere else online.