Sunday, 20 January 2013

ASP.NET MVC 4, ADFS 2.0 and 3rd party STS integration (IdentityServer2) – Part 2

This the 2nd part of a 2 part blog series of which we will extend ADFS 2.0 to allow alternative login credentials.  If you would like more information of the objectives of this series please refer to part 1.

Moving forward….

With your application now successfully integrated we can focus on the objective of uplifting ADFS 2.0 to allow 3rd party STS integration.  As part of this series we are utilising Dominick Baier (@leastprivilege) Identity Server which provides us with the extensibility to integrate a with a custom authentication store.

Installing Identity Server

Hopefully by now you will have already configured identity server by following the instructions at http://vimeo.com/51088126, this should leave you with a working understanding of the STS user configuration process.  One thing to be mindful when installing IdentityServer and ADFS on the same server is that the federation-metadata addresses are registered to the ADFS proxy service, this means that the request is sent directly to the ADFS windows service. 

Due to this you are unlikely to be able to obtain the IdentityServer federation metadata required to create the Trusted claims provider exchange automatically.  This makes configuring the Trust a manual process, I would recommend hosting the services on different servers for this reason.

Creating the test account

In order to test the ADFS integration and limit the changes we will create and configure IdentityServer v2 with an out of the box account and configure ADFS as a relying party Trust.  To begin this process we need to create a test user account within Identity Server. 

Browse to the IdentityServer home page and sign in, click the administration link:

image Click Users on the configuration menu.imageClick New and Provide the test user details, ensuring you add the user to the IdentityServerUsers group (this enables the ability for users to login to the STS).imageNow the user is added we need to enable the WS-Trust protocol, WS-Trust provides the ability to connect directly to the STS and is an extension of WS-Security.  WS-Trust enables us to connect directly via a SOAP channel and request tokens.

To enable WS-Trust click Protocols, select WS-Trust and Save Changes.imageTo validate WS-Trust is enabled, click the home link and select Application Integration.  You should now see the addition WS-Trust meta-data and mixed mode security endpoints.imageIdentity Server provides several out of the box claims.  These can be viewed at the federation metadata endpoint address above.

<fed:ClaimTypesOffered>
<auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706"/>
<auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706"/>
<auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706"/>
<auth:ClaimType Uri="http://identityserver.thinktecture.com/claims/profileclaims/twittername" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706"/>
<auth:ClaimType Uri="http://identityserver.thinktecture.com/claims/profileclaims/city" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706"/>
<auth:ClaimType Uri="http://identityserver.thinktecture.com/claims/profileclaims/homepage" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706"/>
</fed:ClaimTypesOffered>



As you can see we have access to standard claims (such as name, emailaddress and role).  The final 3 claims are obtained via configuration twittername, city and homepage.  The configuration for these can be found in the configuration folder in the profile.config file.

<profile automaticSaveEnabled="false"
defaultProvider="Profile">
<providers>
<add name="Profile"
type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
connectionStringName="ProviderDB"
applicationName="/" />
</providers>

<!-- properties that should get turned into claims go here -->
<properties>
<add name="City" />
<add name="HomePage" />
<add name="TwitterName" />
</properties>
</profile>



User profile settings are added using the profile link on the users configuration page.imageWith the User configured we need to configure ADFS as a relying party.  This is achieved via the Relying Parties and Resources Tab.  To configure ADFS we will use the federation passive endpoint address (https://<<ADFSDOMAIN>>/adfs/ls).imageThis concludes the configuration of IdentityServer for processing of claims from within ADFS (for now).  In order for us to complete our mission, we need to uplift our ADFS installation to accept claims from our new STS.


Before we progress, please ensure you have the ability to access to the federation meta-data from your ADFS STS.


ADFS Configuration


Open up the ADFS MMC and Browse to the Trust Relationships, Claims Provider Trusts. imageSelect Add Claims Provider Trust, this will start the Trust Wizard, click Start.  You now have 2 options, import from the Federation Metadata or Browse directly depending on your configuration.  Either upload or browse directly to the meta-data address as below:imageClick Next, Provide a Display Name, Next and Finish.  Ensure you open the Edit Claims Rules Dialog at this point.imageAdd a rule which passes the Name through to ADFS from the Trusted Provider.imageimage Click Finish.


The last step to configure and allow remote Trusts is to configure ADFS as a valid realm audience, this is achieved via Windows Powershell.  Open up the powershell console and run the following script (Replace <<ADFSDOMAIN>> with your ADFS domain.

Add-PSSnapin Microsoft.Adfs.PowerShell
set-ADFSProperties -AcceptableIdentifier "https://<<ADFSDOMAIN>>/adfs/ls"



We now have a fully configured Trust between ADFS and Identity Server.  This trust is pretty useless as we now need to modify FormsSignIn.aspx to establish a login to our 3rd party STS. 


Modifying ADFS FormsSignIn.aspx


ADFS 2.0 on Windows Server 2012 utilises .NET 4.5 and therefore WIF baked into the Framework.  This is the use-case I am working against in this post.  Unfortunately, .NET 4.5 removes the WS Channels which are fundamental to talk to WS-Trust services.  Luckily for us Dominick Baier has captured these from the WIF 3.5 source and exposed them in the IdentityServer GitHub source. 


This source has been refactored into a component dedicated for the purpose of remote Username and Password STS contact.  ADFS Helper (and Source) can be downloaded below.


ADFS Helper.zip


To progress, copy the bin folder to C:\inetpub\adfs\ls.imageimage



Gm.Adfs.Helper includes a method which provides the ability to login to the remote STS without overly modifying the FormsSignIn.aspx.cs.  The core method in the helper is shown below.  Gm.Adfs.Helper.RemoteSignIn.SignIn accepts 7 parameters:



wsTrustAddress = The address of the remote STS (WS-Trust address from application integration).


applicationRealm = The application realm configured in the relying party trust.


username = Authenticating user.


password = Authentication password.


ignoreCertificateErrors = Whether to globally ignore invalid certificates (Non-Trusted publishers).


authenticationCertificateMode = The authentication validation certificate mode.


claims = A parameter array of RequestClaim requests.

        public static SecurityToken SignIn(
string wsTrustAddress,
string applicationRealm,
string username,
string password,
bool ignoreCertificateErrors,
X509CertificateValidationMode authenticationCertificateMode,
params RequestClaim[] claims)
{
if (ignoreCertificateErrors)
{
ServicePointManager.ServerCertificateValidationCallback = delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
};
}

var binding = new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential);
using (var factory = new WSTrustChannelFactory(binding, wsTrustAddress))
{
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = authenticationCertificateMode;

// create token request
var rst = new RequestSecurityToken
{
KeyType = KeyTypes.Bearer,
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(applicationRealm)
};

foreach (var claim in claims)
{
rst.Claims.Add(claim);
}

new RequestClaim(ClaimTypes.Name);

// request token and return
RequestSecurityTokenResponse response;
var output = factory.CreateChannel().Issue(rst, out response);
return output;
}
}



Please ensure you understand the code above before attempting to integrate a 3rd party STS via FormsSignIn.aspx.cs.


With all of the above complete, you are now ready to modify the FormsSignIn.aspx.cs.  Modifying the page is simple, just add the required namespace and the required SubmitButton modification.  The code below shows the modification to the SubmitButton method.  This approach first attempts to login to the remote STS and then falls back to ADFS, enabling both authentication methods to be supported.


Add the following Namespaces to the FormsSignIn.aspx.cs

using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.IdentityModel.Protocols.WSTrust;
using System.Security.Cryptography.X509Certificates;
using System.IdentityModel.Tokens;
using System.Security.Claims;

using Gm.Adfs.Helper;



Modify the SubmitButton with the following code, modifying the OtherSTSAddress and YourSTSAddress variables.

    const string OtherSTSAddress = "https://idserv.riddify.co.uk/issue/wstrust/mixed/username";
const string YourSTSAddress = "https://mcad.riddify.co.uk/adfs/ls";

protected void SubmitButton_Click(object sender, EventArgs e)
{
try
{
try
{
var token = RemoteSignIn.SignIn(
OtherSTSAddress,
YourSTSAddress,
UsernameTextBox.Text,
PasswordTextBox.Text,
true,
X509CertificateValidationMode.None,
new RequestClaim(ClaimTypes.Name));
SignIn(token);
}
catch (FaultException fex)
{
SignIn(UsernameTextBox.Text, PasswordTextBox.Text);
}
}
catch (AuthenticationFailedException ex)
{
HandleError(ex.Message);
}
//catch (Exception ex)
//{
// Response.Write(ex.ToString());
//}
}

Please take note of the RequestClaims line of code, this can be modified to enable custom claims to be progressed through ADFS.  Below I have modified the code to show a request for twitter account from IdentityServer.

            var token = RemoteSignIn.SignIn(
OtherSTSAddress,
YourSTSAddress,
UsernameTextBox.Text,
PasswordTextBox.Text,
true,
X509CertificateValidationMode.None,
new RequestClaim(ClaimTypes.Name),
new RequestClaim("http://identityserver.thinktecture.com/claims/profileclaims/twittername"));
SignIn(token);



These claims will all need to be configured to pass through to your receiving relying party, so please do not forget!!!  This was discussed in part one but needs to be configured using a Relying Party Claim Rule.


image With the above all complete let’s modify our MVC application to obtain the Twitter name from the claim.


Modifying ASP.NET MVC Test Page.


With the above complete we can modify out ASP.NET MVC application to populate the ViewBag with both the Name and Twitter claim.

        public ActionResult Index()
{
ViewBag.Identity = Thread.CurrentPrincipal.Identity;

var claims = Thread.CurrentPrincipal.Identity as System.Security.Claims.ClaimsIdentity;
var twitter = claims.Claims.FirstOrDefault(p => p.Type == "http://identityserver.thinktecture.com/claims/profileclaims/twittername");

ViewBag.Twitter = twitter.Value;

return View();
}



Which outputs the following into the view.

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@ViewBag.Identity.Name
</p>
<p>
@ViewBag.Twitter
</p>

<p>
@User.Identity.AuthenticationType
</p>



To screen.


image


Thank you for reading through this series.  I hope you have reached an understanding of customising ADFS for custom authentication login.


My next blog post will expose the methods used to customise identity server, replacing IUserRepository and IClaimsRepository.

Sunday, 13 January 2013

ASP.NET MVC 4, ADFS 2.0 and 3rd party STS integration (IdentityServer2)

Introduction

I am currently going through the architectural process of enabling 3rd party claims authentication via both active directory and a custom authentication store.  This is a common requirement for the enterprise where a users primary login is not necessarily active directory.  This complex scenario enables a single user to login via both an application account or the windows account, providing ultimate authentication flexibility.

So, how is this achieved via ADFS 2.0?.. The honest answer is it isn’t.  ADFS does not provide a pluggable model for custom authentication and only supports active directory authentication out of the box.  Custom authentication needs to be provided via the utilisation of a custom Trusted STS and ADFS 2.0.  Producing a reliable STS is no simple feat, the management, security and protocol support required to produce a working token service is a lot of work.  Luckily for us, Dominick Baier (@leastprivilege) has taken all of this pain away via Identity Server 2.0. 

Identity server 2.0 is an Open Source STS providing the ability to “swap out” elements of the architecture and wrap a custom identity store.  Once wrapped, this store can be accessed via the claims protocols (WS-Trust) and provide alternative authentication tokens.  These tokens can be used to sign-in to ADFS and authenticate the user.  With authentication complete, subsequent claims transforms can be applied inside ADFS.  This is a challenging use-case, not one for the feint hearted.

In the first part of this 2 part series I will configure ADFS 2.0 and Setup the relying party trust to an ASP.NET MVC application.  In the 2nd part in the series I will install identity server v2 and modify the ADFS forms process to delegate access to the identity server provider via WS-Trust.

Environment

To get started you will need a Windows 2012 domain server, Windows 8 (IIS configured with a self-signed cert) and Visual Studio 2012 along with Identity Server 2.0.  Knowledge of ASP.NET MVC 4.0 and Windows Identity Foundation is a bonus.

Identity Server can be downloaded from here.

  • On the Windows 2012 Server, Add the ADFS 2.0 role and configure a self-signed IIS certificate with the correct bindings.  This is explained exceptionally on the sysadminblog. Installing ADFS 2.0
  • Once installed, install and configure identity server as described in the video at http://vimeo.com/51088126.
  • .NET 4.5 and Windows 8 ship with WIF.
  • Visual Studio 2012 ships with ASP.NET MVC 4.0, to make life easy to you enable the “Identity and Access Tool” from Tools, Extensions and Updates extension manager.  This provides an application level menu to configure your .NET application against the new federation service.

Configuring ADFS 2.0 for Forms Authentication

ADFS 2.0 by default is configured to hook directly into your existing active directory via integrated security through the browser.  To utilise the ability to login to the 3rd party STS (IdentityServer) you need to change the default configuration to Forms (more on this in part 2).  This is achieved by modifying the ADFS web.config (Found at c:\inetpub\adfs\ls\web.config) authentication preference (as below).  You can also achieve this directly by embedding the authentication type into authenticating applications url via the wauth parameter.  I.E: https://windows2012/adfs/ls?wauth=Forms

<microsoft.identityServer.web>
<localAuthenticationTypes>
<add name="Forms" page="FormsSignIn.aspx" />
<add name="Integrated" page="auth/integrated/" />
<add name="TlsClient" page="auth/sslclient/" />
<add name="Basic" page="auth/basic/" />
</localAuthenticationTypes>
<commonDomainCookie writer="" reader="" />
<context hidden="true" />
<error page="Error.aspx" />
<acceptedFederationProtocols saml="true" wsFederation="true" />
<homeRealmDiscovery page="HomeRealmDiscovery.aspx" />
<persistIdentityProviderInformation enabled="true" lifetimeInDays="30" />
<singleSignOn enabled="true" />
</microsoft.identityServer.web>



Take note of the FormsSignIn.aspx page, this one of very few customisation points in ADFS 2.0.  We will be using this later to redirect our credentials to our 3rd party STS.

 

With ADFS configured, take note of the metadata address from the ADFS management console.  This is found under the service, endpoints folder, for the purposes of this post we will use the Federation Metadata endpoint.  As you can’t copy this from the MMC, here is the endpoint suffix:

 

/FederationMetadata/2007-06/FederationMetadata.xml

 

image

 

Creating the ASP.NET MVC 4.0 Application


In order to test the demonstration we will need a sample harness.  An easy option to demonstrate this process is to create our own claims enabled application.  To create this application launch visual studio (as an Administrator) and create a new blank basic ASP.NET MVC 4 application.  Add this application to IIS, this makes the process easier. 


Please ensure you have added the “Identity and Access Tool” from Tools, Extensions and Updates extension manager.


image


In the project options ensure your application is using IIS.  This process does work under IIS Express, this is my personal preference as configuring certificates is easier inside IIS.


image 


Once loaded, right click on the project and select the Identity and Access option.


imageOnce clicked, you will need the metadata URL from above, everything else will be configured automatically based on your application URL.  You may desire to change the application realm (configuration tab), this can be done after metadata configuration.


imageOnce complete your web.config will be modified.  The modifications provide the details of the identity provider and changes the application authentication settings and paths to allow for claims authentication.  This is a fairly self-explanatory configuration consisting of the audience (your application root URL), The public certificate thumbnail of the issuing identity provider, and the redirection address to the identity provider for authentication (issuer).  Realm is the relying party (service-provider) key (name) given to the identity provider to identify your application.


  <system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="http://localhost/Claims.Test/" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedIssuers>
<add thumbprint="THUMBNAIL OF CERT" name="http://IdServ/adfs/services/trust" />
</trustedIssuers>
</issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" />
<wsFederation passiveRedirectEnabled="true" issuer="https://IdServ/adfs/ls/" realm="http://localhost/Claims.Test/" requireHttps="false" />
</federationConfiguration>
</system.identityModel.services>



The identity and access tool also adds the following settings to your application settings section.


    <add key="ida:FederationMetadataLocation" value="https://idserv/FederationMetadata/2007-06/FederationMetadata.xml" />
<add key="ida:Issuer" value="https://Idserv/adfs/ls/" />
<add key="ida:ProviderSelection" value="productionSTS" />



With the above complete, when you run the application you should be presented with the default ADFS login authentication homepage as below.  At this point the login process will fail as you have not configured the “relying party trust” in ADFS 2.0.  ADFS does not know whether the user has access to the relying application.  As articulated in my previous post, relying party is a MS linguistic, service-provider is more environment agnostic.image


imageNow your application is configured you need to configure the relying party trust in ADFS 2.0.  On your ADFS server, browse to the ADFS 2.0 MMC and select Relying Party Trusts, Add Relying Party Trust.  This will launch the relying party configuration wizard.


image


Screen 1, enter data manually


image


Screen 2, Give your application a meaning full name and description.  image


Screen 3, Select ADFS profile image Screen 4, Click next.  This screen is used to configure the public key provided by your application to encrypt the token send from the identity provider.  For the purposes of this post we will not be encrypting tokens.


imageScreen 5, provide the endpoint URL for your relying application.  This is the home URL as WIF utilises the ASP.NET authentication module architecture to intercept and validate tokens.  For this demo I am working from a host called Windows 8.


imageScreen 6, add the trust identifier (this is the realm as configured above).  The realm is send in the federation url to determine the relying party configuration to utilise at the IDP.


 imageScreen 7, configure the users to access the relying party (service-provider).  Permit all users initially, this can be changed by utilising authorisation rules in ADFS.


imageScreen 8, Verify your settings and save the Trust, ensuring you open the claim Edit claim rules dialog.


You will now be able to login to your application, although at this point no claims have been configured so it is rather useless.  The next dialog provides you the ability to configure and forward claims to the relying application. Without any claims your application will see no authentication properties.  Below I configure the Name claim to be sent to the ASP.NET application, this is then presented in the ASP.NET Thread.CurrentPrincipal.Name property.


image


Add Rule (Opens Rule Template Wizard)


image


Pass Through or Filter Claim Selection.


image


Call the rule “Send Name” and select the Name claim type.  Click Finish


With this now configured your application will be able to login and you should be able to obtain the claim name presented within ASP.NET.  If you used the blank ASP.NET MVC 4 template, add a new home controller and copy the code below into the Home View.


@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@System.Threading.Thread.CurrentPrincipal.Identity.Name
</p>

<p>
@User.Identity.AuthenticationType
</p>



Once launched and logged in, you have completed the ADFS 2.0 application configuration process.  Well Done!


image


image


Part 2, takes this to another level by modifying the ADFS 2.0 FormsSignIn.aspx to delegate authentication to Dominick Baiers (@leastprivilege) Identity Server v2, enabling custom credential stores.