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>

SAML signout

Overview

In a previous post I show how to use Component Space to add SAML support to the Web Client, here I add a sign-out button.

Steps

useADFS == true

Set useADFS to true in hprmServiceAPI.config, for example:

<setup databaseId="J1"  searchAhead="false" advancedSearch="false" workpath="C:\HP Records Manager\ServiceAPIWorkpath\Uploads" useADFS="true"/>

Setup signing certificate

If you plan to sign your logout request then you will need a certificate, either encrypt the password or store the certificate in the Windows certificate store as described in this Component Space document.

Add keep-alive

To allow for notification when the user's session has expired add the keep alive loop.  To do this edit _Initialisation.cshtml (or _Shared.cshtml in later versions) and comment out the RMStayALive function, then add this new version:

var RMStayALive2 = function () {
    var makeRequest = function () {
        $.getJSON(HP.HPTRIM.TrimClient.getServiceAPIUrl() + "/Location/me", function (data, status, xhr) {
                
        }).fail(function () { 
            if (confirm("Your session has expired, do you wish to re-authenticate?")) {
                top.location = HPRMWebConfig.virtualDirectory;
            }
            
        });
    }
    setInterval(makeRequest, (60 * 1000));

}();

Video

The process above is shown in this video.

Custom Authentication

A couple of years I wrote a post describing Google authentication in the ServiceAPI.  This is an update of that.  The details are in the samples repo and here is a video of me applying the sample from the repo to a WebDrawer and Web Client instance.

Other authentication providers

Remember the repo contains a link the ServiceStack documentation which provides assistance for using (and creating) other authentication providers.

ServiceAPI impersonation

The configuration section of the ServiceAPI help documents a handy little property called 'trustedToImpersonate'.  This allows your ServiceAPI to choose to trust one or more calling services.

Scenario

This can be useful in a server to server scenario where passing the actual user credentials to the ServiceAPI is not practical.

Configuration

To allow a particular account to impersonate others users set their name in the hptrim.config file as seen below.  TrustedToImpersonate is a regular expression so you can list multiple accounts if you need to.

<hptrim
  serviceFeatures="Razor,Html,Json,Xml,PredefinedRoutes" 
  trustedToImpersonate="trim\\davidc"
  ...
</hptrim>       

Usage

To impersonate someone using the .Net client librarys use the SetUserToImpersonate() method.

TrimClient client = new TrimClient("http://MyServer/ServiceAPI");
client.Credentials = new NetworkCredential("davidc", "my password", "trim");
client.SetUserToImpersonate("someone\\else");

To impersonate from a different context simply send an HTTP header named 'userToImpersonate' with the value being the user's name along with each request.

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/"

Google authentication

Introductory thoughts

The ServiceAPI (upon which WebDrawer and the Web Client are based) has a pluggable authentication module.  This allows for the support of a variety of identity providers (IdP).  For those considering Google as IdP this is the first step in that direction.

Breaking News!

While doing some testing in 8.2 I found a regression that means plugins will not work in the 82 Web Client (WebDrawer and ServiceAPI or OK).  This should be fixed in the first 82 patch.  So be warned, 811 should be fine but not 82, skip straight to 8.2 Patch 1 if you want to use Google authentication in the Web Client.

Looks simple to me

In which I demonstrate the process to:

  • create a Google Client Id
  • add the appropriate settings to the WebDrawer config files, and
  • copy the required DLLs.

Secrets

You may note I do not make an effort to obscure the various bits of secret information in my Google Developer console.  Don't worry, I have re-set the sensitive bits.

Files

Available to download are the:

HPRM 82

The code remains the same but in HPRM 82 some interfaces have changed, use these for 82 instances:

Source of the source

For those interested the source for the above plugin all comes from ServiceStack framework (V3), look in the ServiceStack.Authentication.OAuth2 project and ServiceStack.ServiceInterface/Auth.

Steps

The steps I follow in the video above include:

Create a Google Client Id

Go to console.developers.google.com/project to create a project then create a client id for a web application

Edit the config files

In the WebDrawer web.config I add this in the appSettings element, making sure to use the correct WebDrawer URL of course:

<add key="oauth.RedirectUrl" value="https://811x.hprm.info/GWebDrawer/"/>
<add key="oauth.CallbackUrl" value="https://811x.hprm.info/GWebDrawer/auth/{0}"/>
<add key="oauth.GoogleOAuth.ConsumerKey" value="YOUR_CLIENT_ID"/>
<add key="oauth.GoogleOAuth.ConsumerSecret" value="YOUR_SECRET"/>

In hptrim.config I add this:

<pluginAssemblies>
  <add name="GoogleAuthPlugin" />
</pluginAssemblies>

Copy the DLLs

Lastly I copy the DLLs into the WebDrawer\bin folder.

For the Web Client only

The Web Client embeds the ServiceAPI slightly differently to WebDrawer, for this reason you will need to add a login element to appSettings (in web.config), just like this one.

<add key="oauth.login" value="~/HPRMServiceAPI/auth/GoogleOAuth" />

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>