Authenticate the Web Client with Azure AD

I have a few posts demonstrating on-premise ADFS with the CM Web Client but so far nothing with Azure Active Directory, today I rectify that.

The Video

Steps

The steps to configure the web client to use Azure AD are:

  • configure the site to use anonymous in IIS Admin,
  • create the Azure Ad App Registration, and
  • edit the Web Client web.config.

Sample Config

configSections

<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral,PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

appSettings

<add key="ida:FederationMetadataLocation" value="https://login.windows.net/[Your Tenant ID]/FederationMetadata/2007-06/FederationMetadata.xml" />

authorization and authentication

<authorization>
  <deny users="?" />
</authorization>
<authentication mode="None" />

system.identityModel

<system.identityModel>
<identityConfiguration>
  <audienceUris>
    <add value="[APP ID URI]" />
  </audienceUris>
  <securityTokenHandlers>
    <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </securityTokenHandlers>
  <certificateValidation certificateValidationMode="None" />
  <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
    <authority name="https://sts.windows.net/[Tenant ID]/">   
      <validIssuers>
        <add name="https://sts.windows.net/[Tenant ID]/" />
      </validIssuers>
    </authority>
  </issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
  <cookieHandler requireSsl="true" />
  <wsFederation passiveRedirectEnabled="true" issuer="https://login.windows.net/[Domain]/wsfed" realm="[APP ID URI]" requireHttps="true" />
</federationConfiguration>
</system.identityModel.services>

WebClient ADFS setup for Office Integrations

Overview

To configure the Web Client to use ADFS follow the same steps as those required for WebDrawer.  If in addition to using ADFS via a web browser you wish to use the Office addins with the Web Client / ServiceAPI you will need to do some additional configuration.  This is to support the OAuth authentication required by client applications.  Use the powershell command Add-ADFSClient on your ADFS server to create the ADFS client, described in this post.

Warning

Be careful with case.  Uris are case sensitive and if you get this wrong everything will be broken.

Things to look for in web.config

The location for the Integration path should allow anonymous access as the authorization is handled by a bearer token sent by the Office Addin.

<location path="Integration">
  <system.web>
    <authorization>
      <allow users="?" />
    </authorization>
  </system.web>
</location>

Also, inside the location path for "hprmserviceapi" ensure that the authorization is similarly set.

Near the top of web.config is the configuration element containing configSections, this should contain the two sections below.

<section 
  name="system.identityModel" 
   type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

<section 
    name="system.identityModel.services"
    type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

The appSetting will probably contain the ida:FederationMetadataLocation property if you are authenticating with the WebCleitn using ADFS.  It is not necessary for the Outlook integration wo work.

<add key="ida:FederationMetadataLocation" value="https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml" />

Make sure the system.identityModel and system.identityModel.services have been added and configured, for example:

<system.identityModel>
  <identityConfiguration>
    <audienceUris>
      <add value="https://giri12012r2.trim.lab/HPEContentManager/" />
      <add value="https://giri12012r2/HPEContentManager/" />
    </audienceUris>
    <securityTokenHandlers>
      <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    </securityTokenHandlers>
    <certificateValidation certificateValidationMode="None" />
    <!--Element below commented by: ValidatingIssuerNameRegistry.WriteToConfg on: '24/06/2016 5:12:27 AM (UTC)'. Differences were found in the Metatdata from: 'https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml'.-->
    <!--<issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry"><authority name="http://ADFS1.testteam.local/adfs/services/trust"><keys><add thumbprint="538B9CF4D293995C9406D7EA74C3F7353C5DD62A" /></keys><validIssuers><add name="http://ADFS1.testteam.local/adfs/services/trust" /></validIssuers></authority></issuerNameRegistry>-->
    <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
      <authority name="http://ADFS1.testteam.local/adfs/services/trust">
        <keys>
          <add thumbprint="1247E2A79A46AD1EB71BFEA4582C0FB9465EE9BE" />
        </keys>
        <validIssuers>
          <add name="http://ADFS1.testteam.local/adfs/services/trust" />
        </validIssuers>
      </authority>
    </issuerNameRegistry>
  </identityConfiguration>
</system.identityModel>
<system.identityModel.services>
  <federationConfiguration>
    <cookieHandler requireSsl="true" />
    <wsFederation passiveRedirectEnabled="true" issuer="https://adfs1.testteam.local/adfs/ls" realm="https://giri12012r2.trim.lab/HPEContentManager/"  requireHttps="true" />
  </federationConfiguration>
</system.identityModel.services>

I discuss this in more detail when describing ADFS setup for WebDrawer.  The key points to consider are:

  • replace the audienceUri with one that is valid id for your relying party trust
  • ensure that the authority name points to your ADFS server
  • replace the issuer and realm with your ADFS server name and your realm

Things to look for in hprmServiceAPI.config

The <setup /> element has an attibute called useADFS, which would better be called showADFSLogout.  Set this to true to show logout link in the user menum as seen here.

The authentication section gives the WebClient the information it requires to use OAuth authentication for the UI components.  The audience should match the identifier in your relying party trust and the federation endpoint is the same as that in ida:FederationMetadataLocation. 

<authentication allowAnonymous="false">
  <activeDirectory>
    <add 
         name="adfs" 
         audience="https://giri12012r2.trim.lab/HPEContentManager/" 
         metadataEndpoint="https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml"/>
  </activeDirectory>
</authentication>

ADFS\config.xml file

Within the WebClient folder you will find an ADFS folder which contains a file called config.xml.  This file stores the information your Office clients need to connect to ADFS.  It must be available via anonymous access so that it can be fetched before authenticatin has happened.  Before you will have the values required for this file you will need to have configured and ADFS client as descirbed in this post. The four values in this file are:

ElementSource
clientAuthority The address of your adfs instance.
Example
https://adfs1.testteam.local/adfs
clientResourceUri The identifier of your relying party trust.
Example
https://MyServer/MyWebClient/
clientID The guid used in the client created by Add-ADFSClient.
clientReturnUri The RedirectUri specified when running Add-ADFSClient.

ADFS and the trailing slash

The problem

Those who have experimented with ADFS authentication for the web client (or Web Drawer) may have noticed that if the trailing slash is not included in the URL then the authentication fails.

The solution

The solution is to include a trailing slash.

The details

What is actually going on here is not, technically a bug, but a feature.  The slashes in the URL separate out the realms. So when the trailing slash is ommitted ADFS considers that rather than authenticating with the HPRMWebClient realm you are actually opening the page HPRMWebClient in the www.myserver.com realm.

A 'better' solution?

I have experimented with this a fair bit, one solution would be for us to re-design the Web Client so that, for example, the base URL was /HPRMWebClient/home.  This may be a solution for the future but what about the present.  IIS URL re-write had some promise but from what I can see the ADFS authentication module kicks in before the rewrite.

So...

This post on stackoverflow provided a handy shortcut to a solution, override the authentication module to append the trailing slash if it is missing.  I have added the code, or you can download the DLL. To use this:

  1. copy the DLL to the web client bin folder,
  2. edit your web.config modules section to look like the below, and
  3. ensure that the identifier in your ADFS relying party trust is the same as your web client base URL (e.g. https://myserver.com/HPRMWebClient/)
<modules>
  <add name="FixedWSFederationAuthenticationModule" type="FixedWSFederationAuthenticationModule.FixedWSFederationAuthenticationModule, FixedWSFederationAuthenticationModule" preCondition="managedHandler" />
  <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>

An update on the Web Client and Web Client classic

If you deal with the trailing slash using the technique above in both the web client and web client classic then you will have one more thing to do.  The web client enables linking to the web client classic, if the base address does not include the trailing slash this will fail.  To fix this:

  1. edit hprmServiceAPI.config in the web client folder,
  2. find the setup element, then
  3. change the webClientClassicUrl to include a trailing slash.

For example:

<setup 
    databaseId="T1" 
    webClientClassicUrl="/HPRMWebClientClassic/" 
    searchAhead="false" 
    advancedSearch="false" 
    workpath="C:\HP Records Manager\ServiceAPIWorkpath\Uploads"/>

ADFS claim rule mapping

Map User-Principal-Name to Name

This is a for those of you who notice a gap in the current RM Web Client ADFS configuration documentation.  The documentation for the RM native client specifies the Relying Party claim rule should have the LDAP attribute User-Principle-Name mapped to the outgoing claim UPN.  The Web Client documentation contains a simple custom rule but does not specify exactly which claims are are required.  Unlike the native client the User-Principal-Name must be mapped to the outgoing claim Name, like this:

Some good news

In a an upcoming version we plan to support UPN for the web client, which will check UPN first and then Name, using whichever is sent by ADFS.

ADFS - Web Client Classic

In another post I have examined how to configure ADFS for WebDrawer, the process is very similar for Web Client Classic as can be seen in this video.

 

Additional steps

Below I list all the configuration that requires modification, in addition to this you also need to:

  • add a relying party trust in ADFS,
  • enable anonymous authentication in IIS, and
  • copy the file System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.dll from your Web Client\bin folder to Web Client Classic\bin.

I look at both of these in the video above.

Config to copy, paste and modify

Overview

All of the modifications are made in the Web Client Classic web.config file.

configSections

Copy this into the configSections element.

<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

appSettings

Create an appSettings element and replace the URL to our ADFS server with your ADFS server.

<appSettings file="user.config">
    <add key="ida:FederationMetadataLocation" value="https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml" />
</appSettings>

authorization and authentication

These go inside the system.Web element, make sure you remove the existing authentication element.

<authorization>
    <deny users="?" />
</authorization>
<authentication mode="None" />

Modules

Inside the system.webServer/modules element add these two modules.

<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />

WIFHandler

Add this location inside the root (configuration) element.

<location path="WIFHandler">
  <system.web>
    <httpRuntime requestValidationMode="2.0" />
  </system.web>
</location>

webHttpBinding

Find your webHttpBinding element and modify it to look like this.

<webHttpBinding>
<binding name="hptrimBinding" sendTimeout="00.10:00">
    <security mode="Transport">
    <transport clientCredentialType="None"/>
  </security>
</binding>
</webHttpBinding>

system.identityModel

The XML below is taken directly from my web.config file so you will need to find all of the URLs pointing to my Web Client Classic and ADFS server and change them.  You will also need to use the powershell command  Get-AdfsCertificate to get your thumbprint.

<system.identityModel>
    <identityConfiguration>
        <audienceUris>
            <add value="https://rmcloudweb.testteam.local/HPRMWebClientClassic/" />
        </audienceUris>
        <securityTokenHandlers>
            <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
            <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        </securityTokenHandlers>
        <certificateValidation certificateValidationMode="None" /> 
        <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
            <authority name="http://ADFS1.testteam.local/adfs/services/trust">
                <keys>
                    <add thumbprint="538B9CF4D293995C9406D7EA74C3F7353C5DD62A" />
                </keys>
                <validIssuers>
                    <add name="http://ADFS1.testteam.local/adfs/services/trust" />
                </validIssuers>
            </authority>
        </issuerNameRegistry>
    </identityConfiguration>
</system.identityModel>
<system.identityModel.services>
    <federationConfiguration>
        <cookieHandler requireSsl="true" />
        <wsFederation passiveRedirectEnabled="true" issuer="https://adfs1.testteam.local/adfs/ls" realm="https://rmcloudweb.testteam.local/HPRMWebClientClassic/" reply="https://rmcloudweb.testteam.local/HPRMWebClientClassic/WIFHandler" requireHttps="true" />
    </federationConfiguration>
</system.identityModel.services>

ADFS from a .Net SDK application

Overview 

In a previous post I examined how to configure RM to authenticate via ADFS when using the native client, in this post I show how to write a .Net SDK application to authenticate via ADFS.

The code

This is the console application I used in the video above

class Program
{

    private static ClientAuthenticationMechanism getAuthenticationMechanism(string dbId)
    {
        foreach (int authenticationMethod in new int[] { 3, 2, 0 })
        {
            if (Database.IsAuthenticationMethodSupported("G1", (ClientAuthenticationMechanism)authenticationMethod))
            {
                return (ClientAuthenticationMechanism)authenticationMethod;
            }
        }

        throw new ApplicationException("No Authentication method found.");
    }


    static void Main(string[] args)
    {
        TrimApplication.TrimBinariesLoadPath = "D:\\82\\x64\\Debug";
        TrimApplication.HasUserInterface = true;
        TrimApplication.Initialize();

        using (Database database = new Database())
        {
            database.Id = "G1";
            database.WorkgroupServerName = "local";
            database.AuthenticationMethod = getAuthenticationMechanism("G1");

            database.Connect();


            Console.WriteLine("{0} - {1}", database.CurrentUser.FullFormattedName, database.CurrentUser.Uri);
        }
    }
}

Warning

As I mention in the video the method Database.IsAuthenticationMethodSupported will only return a valid response if the native client has connected previously from the current machine.  If no connection has been made previously then all authentication mechanisms will return true.

ADFS HP RM Client authentication

Overview

In HP Records Manager 8.2 we added ADFS authentication as an option in the native windows client.  In this video I run through configuring this in our lab environment.

Things to copy and paste

Here are the various powershell commands I used on my ADFS server.

Create the ADFS client for HPRM.

Add-AdfsClient -Name "HPRM ADFS Client" -ClientId "ab762716-544d-4aeb-a526-687b73838a33" -RedirectUri "urn:ietf:wg:oauth:2.0:oob" -Description "OAuth 2.0 client for HPRM"

Set the token lifetime to force HP RM to check back with ADFS at defined intervals.

Set-AdfsRelyingPartyTrust -TargetName "My Relying Party Trust" -TokenLifetime 10

Tell ADFS to issue refresh tokens to all devices, you may also choose to specify WorkplaceJoinedDevices.

Set-AdfsRelyingPartyTrust -TargetName "My Relying Party Trust" -IssueOAuthRefreshTokensTo AllDevices

In the video I set the refresh token life in the UI but it can also be done with this powershell command. the maximum value ADFs will allow is 9999 minutes.

Set-AdfsProperties -SSOLifetime 480

ADFS client side authentication (part 2)

Demo

This demonstrates the creation of a simple console application which uses ADFS to authenticate and then passes those credentials to the ServiceAPI.  If you are interested in ADAL then read Vittorio Bertocci's blog, particularly this post.  The token cache I use in the below video can be found here, the code I wrote below is here.

Refresh tokens

A refresh token will allow a client to keep their credentials cached for days rather than hours however in my experience ADFS does not issue refresh tokens by default. You may wish to do some research on refresh tokens and decide whether or not you want to support them.  If yes then use the following powershel command to enable them for your relying party trust.  You can choose to issue refresh tokens to AllDevices or WorkplaceJoinedDevices.

Set-AdfsRelyingPartyTrust -TargetName "davidc2012 ServiceAPI" -IssueOAuthRefreshTokensTo AllDevices

ADFS client side authentication (part 1)

Note

In version 8.3 and later it is not required that you update the ServiceAPI itself to support OAuth as it will support it by default.  As long as you send the Bearer token in the Authentication header and set up the <authentication> element in hptrim.config.

Demo

In this video I configure the ServiceAPI  to force it to use my ADFS instance for authentication for client side applications.  This is achieved by using the OWIN framework to enable OAuth2 in the ServiceAPI instance.  I also look briefly at what is required on the ADFS side to make all this work.

The Code

Here are the resources I used in the video:

The command I used to create the ADFS client:

Add-ADFSClient -Name "MySAPIClient" -ClientId "A1CF1107-FF90-4228-93BF-26052DD2C714" -RedirectUri "https://davidc2012.trim.lab/HPRMServiceAPI/"

Simple WebDrawer ADFS

Background

Setting up a connection between WebDrawer and one of our lab ADFS servers is something I only do occasionally.   Now I am documenting it for the next time I do it.  This is a very simple setup so I do not promise that it will work the same in your environment.

Watch me

The config

Here I detail the various changes made to the WebDrawer web.config in the previous video.

configSections

We identify the two sections we will be configuring later in web.config in the configSections element (usually at the top of web.config)

<configSections>
  ...
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
  
</configSections>

appSettings

In appSettings we provide the location of the ADFS metadata to allow WebDrawer to interrogate this later.

<appSettings file="user.config">
...
<add key="ida:FederationMetadataLocation" value="https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml" />
  
</appSettings>

authorization

Here we deny access to anonymous users (thus requiring authentication and disable standard windows authentication.

<authorization>
   <deny users="?" />
</authorization>
<authentication mode="None" />

system.IdentityModel

I usually add the system.IdentityModel section just before system.webServer, although I believe it may go anywhere.

<system.identityModel>
<identityConfiguration>
  <audienceUris>
    <add value="[Your WebDrawer URL]" />
  </audienceUris>
  <securityTokenHandlers>
    <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </securityTokenHandlers>
  <certificateValidation certificateValidationMode="None" /> 
  <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
    <authority name="http://[Your ADFS Server]/adfs/services/trust">
      <keys>
      </keys>
      <validIssuers>
        <add name="http://[Your ADFS Server]/adfs/services/trust" />
      </validIssuers>
    </authority>
  </issuerNameRegistry>
</identityConfiguration>
</system.identityModel>

modules

The modules go inside system.webServer, I usually place them just after the handlers.

<modules>
  <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
  <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>

WIFHandler

I add this location to avoid a validation error triggered by the re-direct back from the ADFS login page.

<location path="WIFHandler">
  <system.web>
    <httpRuntime requestValidationMode="2.0" />
  </system.web>
</location>

system.identityModel.services

<system.identityModel.services>
  <federationConfiguration>
    <cookieHandler requireSsl="true" />
    <wsFederation passiveRedirectEnabled="true" issuer="https://[Your ADFS Server]/adfs/ls" realm="[Your Realm]" reply="https://[Your WebDrawer URL/WIFHandler" requireHttps="true" />
  </federationConfiguration>
</system.identityModel.services>