Changes in ADFS 2.1 from ADFS 2.0

Well it has been a while since I last posted. I have been sitting on a couple things and wanted to get this information out there.

Recently I had to work on an ADFS 2.0 to 2.1 migration. There is apparently not any supported easy way to upgrade an ADFS environment to work on Windows Server 2012 with SQL Server 2012. I had to recreate all of the ADFS artifacts such as claim provider trusts, relying party trusts, attribute stores, etc. This is a little painful if you created all of these manually but is much easier if you have saved off PowerShell scripts for creating these objects.

In this post I am blogging about the changes I uncovered working with ADFS 2.1. Most of the changes to ADFS in 2.1 are relatively trivial. In my experience almost everything appears the same in the user interfaces for ADFS. Now the ADFS installation is a role rather than a separate hotfix installer and is part of the base Windows Server 2012 install.

If you have saved off any scripts or other tools for working with ADFS 2.0, you will need to update these for ADFS 2.1. Changes that will be required are:

  • The ADFS PowerShell snap-in is no longer required to be added manually. This was my experience with having the PowerShell 3.0 feature installed. So any lines such as the following lines below can just be removed:

Add-PSSnapin Microsoft.ADFS.PowerShell
Remove-PSSnapin Microsoft.ADFS.PowerShell

  • Also, the PowerShell 3.0 ISE tool now includes Intellisense-like support so it is possible to enter cmdlet arguments much easier. This is a huge help.
  • The folder of the ADFS files is now at C:\Windows\ADFS rather than C:\Program Files\Active Directory Federation Services 2.0. If you use a script to call fsconfig.exe you will need to update the script with this new path to fsconfig.exe.
  • The custom claim rules policies base class is now in a .NET 4 assembly so you will be required to update all assemblies that reference this base class to .NET 4.0. So any classes that derive from Microsoft.IdentityServer.ClaimsPolicy.dll must have their build configuration updated to be .NET 4.0 or later.

Update!

I did find another change with ADFS 2.1. If you have made any customizations to the web.config file of the ADFS virtual directory, you will need to update the version details in the web.config as well as remove the reference to Microsoft.IdentityModel. What I did to update this file was to do the following find/replace tasks on the web.config:

  • Update 3.5.0.0 version details to 4.0.0.0
  • Update 6.1.0.0 version details to 6.2.0.0

I will keep updating this page with any other changes I find with ADFS 2.1. Thanks!

Changing ADFS Proxy App Pool Account

A few days ago I was working on finishing up an ADFS implementation and I had customized quite a bit of the built-in ADFS website pages. I needed to use Windows authentication to access a database, and I realized that the ADFS Proxy website app pool by default runs under Network Service. This was troubling because I did not want to grant permissions to Network Service in the database so I needed to modify this account.

I went through the standard stuff to modify the app pool identity and got this error:

Encountered error during federation passive request.

Additional Data

Exception details:

System.IO.FileNotFoundException: Error reading the C:\Program Files\Active Directory Federation Services 2.0\PT directory.

at System.IO.FileSystemWatcher.StartRaisingEvents()

at Microsoft.IdentityServer.ProxyTrust.ProxyTrustManager.StartTokenWatch()

at Microsoft.IdentityServer.ProxyTrust.ProxyTrustManager.get_Current()

at Microsoft.IdentityServer.PolicyModel.Client.PolicyStoreReadOnlyTransferClient.GetServiceChannel()

at Microsoft.IdentityServer.PolicyModel.Client.PolicyStoreReadOnlyTransferClient.GetState(String serviceObjectType, String mask, FilterData filter, Int32 clientVersionNumber)

at Microsoft.IdentityServer.ProxyConfiguration.ProxyConfigurationReader.GetServiceSettingsData()

at Microsoft.IdentityServer.ProxyConfiguration.ProxyConfigurationReader.GetFederationPassiveConfiguration()

at Microsoft.IdentityServer.Web.PassivePolicyManager.GetPassiveEndpointAbsolutePath()

at Microsoft.IdentityServer.Web.FederationPassiveAuthentication.GetPassiveEndpointAbsolutePath()

So I opened the path at “C:\Program Files\Active Directory Federation Services 2.0\PT” which is the folder for the stored proxy token and granted full control to my domain account user. The file written to this directory is constantly updated, so the account does need to be able to remove the file. By default the Network Service account has full control, most likely because the ADFS proxy Windows service also runs under Network Service.

Then I just restarted IIS and this worked.

Thanks

Export Powershell Objects to Xml – in a more natural format than Export-CliXml

Introduction

On a recent project I was calling a PowerShell script and wanted to return Xml from the script so that I could iterate on an object using Linq to Xml. I investigated options available for exporting an object in Powershell to Xml and looked at some built-in cmdlets like Export-CliXml: http://technet.microsoft.com/en-us/library/dd347657.aspx, but I could not get over how complicated my Linq to Xml query would have to be for relatively simple objects. The Export-CliXml format is extremely verbose and is not really easy to work with once you get the Xml out.

Technique

The following snippet shows how to output the Xml and uses custom serialization to Xml for the SamlEndpoint type which shows up as the type name until you drill into the type to get the property information.

function DumpObjectToXml($obj) {
$a = $obj

$openingTag = "<" + $a.GetType().Name + ">"

$ret = $ret + $openingTag

Get-Member -InputObject $a -MemberType Properties | ForEach-Object {

$CurrentName = $_.Name

$a.GetType().GetProperties() | ? { $_.Name -eq $CurrentName } | ForEach-Object {

$specialSerialization = $false

# Handle specialized serialization for object properties of parent object

if ($_.Name -eq "SamlEndpoints") {

if ($obj.SamlEndpoints -ne $null) {

$d = $obj.SamlEndpoints | ? { $_.BindingUri -like "*HTTP-POST" }

$val = $d.Location.AbsoluteUri

$specialSerialization= $true

}

else {

$val = ""

}

}

if ($_.CanRead -eq $true -and $specialSerialization -eq $false) {

$val = $_.GetValue($a, $null)

}

}

$out = "<" + $_.Name + ">" + $val + "</" + $_.Name + ">"

$ret = $ret + $out

}

$closingTag = "</" + $a.GetType().Name + ">"

$ret = $ret + $closingTag

return $ret

}

This can be helpful for outputting a list of objects returned by another cmdlet as Xml. Here is a simple example that builds a usable Xml element for the relying parties returned from the built-in ADFS cmdlet:


$rpOut = "<RelyingPartyTrusts>"

Get-ADFSRelyingPartyTrust | ForEach-Object { $rpOut = $rpOut + (DumpObjectToXml -obj $_) }

$rpOut = $rpOut + "</RelyingPartyTrusts>"

The resulting output looks like this:

<RelyingPartyTrusts>
<RelyingPartyTrust>
<AutoUpdateEnabled>False</AutoUpdateEnabled>
<ClaimsAccepted>Microsoft.IdentityServer.PowerShell.Resources.ClaimDescription Microsoft.IdentityServer.PowerShell.Resources.ClaimDescription</ClaimsAccepted>
<ConflictWithPublishedPolicy>False</ConflictWithPublishedPolicy>
<DelegationAuthorizationRul
es></DelegationAuthorizationRules>
<Enabled>False</Enabled><EncryptClaims>False</EncryptClaims>
<Identifier>https://myAdfsServer/ClaimsAwareWebAppWithManagedSTS/</Identifier>
...
</RelyingPartyTrust>
</RelyingPartyTrusts>

Conclusion

This technique of using objects returned from PowerShell can be very valuable when you want to report on objects that may only be exposed through a PowerShell interface and no supported or available .NET API. There are some limitations to this approach though. Not all objects returned from some cmdlets have properties that can be read in an isolated, atomic way. When trying this technique with Get-Process errors occur indicating the services must be stopped before reading the properties. I think this is due to the way the cmdlet is coded. I tested this script with many of the ADFS cmdlets and it is working well.

Using Fiddler to trace a SAML IDP Request from ADFS 2.0

Introduction

Over the past couple of days I have been doing some diagnostics with a partner to setup SSO over a SAML HTTP POST using ADFS. The partner is using the SAML ComponentSpace component. An important part of the diagnostics has been collecting the HTTP POST trace and sending this to the partner for diagnostics. This post shows the steps I went through to trace the HTTP POST using Fiddler.

I saw one MSDN thread that mentioned a similar technique to mine but I wanted to document the steps here because it is not trivial or simple.

Walkthrough

This walkthrough assumes you have already downloaded Fiddler and have setup at least one relying party.

  1. Configure IIS so that it can be used with Fiddler tracing and ADFS. See the following TechNet Wiki article for more information: http://social.technet.microsoft.com/wiki/contents/articles/ad-fs-2-0-continuously-prompted-for-credentials-while-using-fiddler-web-debugger.aspx?wa=wsignin1.0&CommentPosted=true. I did not do this step but I am presenting this here in case you run into the problem presented on the link. When authenticating, I checked the box to remember my password.
  2. Configure Fiddler for processing HTTPS. Open the Fiddler options and check the boxes like shown below. Ignoring server certificate errors is optional:
  3. Open the browser and navigate to https://adfsFQDN/adfs/ls/IdpInitiatedSignOn.aspx. This page will look like:
  4. Choose to sign in. You will receive an authentication box, so authenticate with Windows credentials.
  5. Then you will see a windows similar to the one below:
  6. Before clicking Go, open Fiddler so that the trace will be collected.
  7. Then click Go in the browser. The trace as collected in Fiddler will be collected like in the screenshot below (sorry this screenshot is a pretty large file but it shows a lot of important details):
  8. You will want to find the last page that includes the “adfs/ls” in the list in the left window in Fiddler. Click on that one. Then on the right window choose to see it “Raw”.
  9. Next you want to select all of the encoded blob from the RAW window but not all of the HTTP POST. You can select this blob and copy it.
  10. Then click on the Encoder toolbar button, which gives you the ability to unencode the blob. The SAML POST will be Base64 encoded so you have to unencode it to get the unencoded trace. Choose the option “From Base64” to see the unencoded Samlp response as shown below:

Thanks!

Symptoms of a failed ADFS farm install

Problems

This week I worked through what appears to be a situation where an ADFS farm install was not successful or finished incompletely. This event has not been typical in my experience with ADFS so I am simply putting these observations out there for others to be aware of.  After an ADFS farm install had occurred from the command-line, various things about working with it were not working as expected. I looked into the ADFS configuration using the ADFS 2 mmc and found these symptoms. I was not actually the person who did the initial scripted install so I am not aware of what went wrong.

  1. No certificates at all had been selected for the encrypting, token signing, and token decrypting certificates. I know with the UI assisted configuration of ADFS that you must choose a certificate for encrypting and the other 2 are generated ones. For the scripted farm install, I am wondering if there is validation or not or if this was simply a weird event.
  2. Some of the federation endpoint addresses were not showing as expected. For example, the federation metadata address showed as “/FederationMetadata/FederationMetadata.xml” rather than the normal “/FederationMetadata/2007-06/FederationMetadata.xml”.
  3. All of the enabled endpoints were giving me 503 service unavailable errors rather than the 400 bad request errors in the browser. The 400 bad request errors are actually the expected ones. This was very similar to the following old forums thread: http://social.technet.microsoft.com/Forums/en-US/windowsserver2008r2management/thread/ef642548-7c1a-427d-972f-df3dd4f2c829/. The 503 error can occur when the ADFS site is running under the wrong app pool identity but changing this did not resolve the problem for me.
  4. When trying to access the federation metadata page from the address given in step 2, I also received a 503 error. I did not see any more informative error messages in the event logs when the 503 error was occuring.

Resolution

To resolve these issues I simply redid a farm installation by script and this time I was handling the installation myself. I do not think this problem was solely user error but might possibly have been some optional parameters or issues with the fsconfig command-line. The documentation on using fsconfig is somewhat poor so I am guessing there could be some things that could go wrong.

If I encounter this problem again or am able to reproduce it I might try creating some scripts to identify the problem. I am wondering if there might also be other indicators of a failed ADFS install, if you know any please let me know.

Thanks!

ADFS Error Tips: When your federation service name does not match your FQDN

With ADFS I am really starting to think it would be useful to have a way to just run a PowerShell test script to diagnose issues, much like a series of unit tests, maybe call it configuration test. Many of the configuration challenges with ADFS could be diagnosed with some scripts. So I made one here.

A few days ago I was getting the following error on ADFS (single server):

ID1038: The AudienceRestrictionCondition was not valid because the specified Audience is not present in AudienceUris.

Audience: ‘https://<fqdn>/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256&#8217;

at Microsoft.IdentityModel.Tokens.SamlSecurityTokenRequirement.ValidateAudienceRestriction(IList`1 allowedAudienceUris, IList`1 tokenAudiences)

at Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateConditions(Saml2Conditions conditions, Boolean enforceAudienceRestriction)

at Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateToken(SecurityToken token)

at Microsoft.IdentityServer.Service.Tokens.MSISSaml2TokenHandler.ValidateToken(SecurityToken token)

at Microsoft.IdentityModel.Tokens.WrappedSaml2SecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token)

at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token)

at Microsoft.IdentityModel.Tokens.WrappedSamlSecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token)

at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token)

at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver, IList`1 allowedTokenAuthenticators, SecurityTokenAuthenticator&amp; usedTokenAuthenticator)

at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer, SecurityToken encryptionToken, String idInEncryptedForm, TimeSpan timeout)

at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)

at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout, ChannelBinding channelBinding, ExtendedProtectionPolicy extendedProtectionPolicy)

at System.ServiceModel.Security.TransportSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, TimeSpan timeout)

at System.ServiceModel.Security.TransportSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout)

at System.ServiceModel.Security.SecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

at System.ServiceModel.Channels.SecurityChannelListener`1.ServerSecurityChannel`1.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationState)

at System.ServiceModel.Channels.SecurityChannelListener`1.SecurityReplyChannel.ProcessReceivedRequest(RequestContext requestContext, TimeSpan timeout)

at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveRequestAndVerifySecurityAsyncResult.ProcessInnerItem(RequestContext innerItem, TimeSpan timeout)

at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveItemAndVerifySecurityAsyncResult`2.OnInnerReceiveDone()

at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveItemAndVerifySecurityAsyncResult`2.InnerTryReceiveCompletedCallback(IAsyncResult result)

at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)

at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)

at System.ServiceModel.Channels.InputQueue`1.AsyncQueueReader.Set(Item item)

at System.ServiceModel.Channels.InputQueue`1.EnqueueAndDispatch(Item item, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.InputQueue`1.EnqueueAndDispatch(T item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.InputQueueChannel`1.EnqueueAndDispatch(TDisposable item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, ItemDequeuedCallback dequeuedCallback)

at System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(HttpRequestContext context, ItemDequeuedCallback callback)

at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContextCore(IAsyncResult result)

at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)

at System.Net.LazyAsyncResult.Complete(IntPtr userToken)

at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)

at System.Net.ListenerAsyncResult.WaitCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)

at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)


I had already enabled service and server tracing and had made sure all my certificate configuration was correct. Eventually I figured out the problem was that my federation service name was different from my server FQDN. When looking at the server trace I was seeing a different address being shown. This difference also occurs when the federation metadata exposes a different name than the current server FQDN.

Here is a script I made to check for this scenario:

Write-Host "Testing for correct federation service name"

Add-PSSnapin Microsoft.ADFS.PowerShell

$IPconfig = Get-WmiObject Win32_NetworkAdapterConfiguration | where {$_.IPaddress -like "192.*" }  # modify this as-needed

# Iterate and get IP address
$ip = $IPconfig.IPaddress
$fqdn = [System.Net.DNS]::GetHostByAddress($ip)

$fedServiceName = Get-ADFSProperties | Where { $_.Name -eq "HostName" }

if ($fedServiceName -ne $fqdn)  { Write-Host "FAIL: Server FQDN not equal to Federation Service Name." }

Remove-PSSnapin Microsoft.ADFS.PowerShell

ADFS 2 Migration Tips

Introduction

Today I was working on moving my ADFS environment to a separate VM so I could test out a deployment guide I had been working on. On my VM1 I had installed ADFS with Windows Internal Database (WID) in a standalone mode. In my target VM2 I had installed ADFS on SQL 2008 R1 as a farm. I wanted to easily move all of the ADFS configuration and settings to VM2 but found that this is actually fairly difficult.

Attempts

Database Backup / Restore

The first thing I tried to do was to create a backup of the AdfsConfiguration and AdfsArtifactStore databases from VM1 and try to restore them on VM2. Before trying this I made a backup of my existing ADFS databases. Because the SQL version used with WID is SQL 2005 it is not possible to use a .bak file exported from WID and restored on SQL 2008, you get an error. So then I had to detach the files from the WID database and try to reattach the databases on the SQL 2008 VM. I was able to reattach the databases but there were problems afterward.

In order to detach the existing SQL 2008 AdfsConfiguration database on VM2, I had to stop the ADFS 2 service. Then after reattaching the new databases from WID, the ADFS 2 service would not start. So I stopped at this point and switched over to moving the artifacts using scripts. If anyone had any other tips for making this type of migration work, I would appreciate the feedback!

Scripted Move

After not being able to move the data over through database backups, I decided to try to script everything. This approach is just as challenging because you still need to go through all of the objects in the ADFS databases and this can take some time in building out the new scripts. I realized a tool that can generate the scripts for you would be a huge time saver.

Some of the PowerShell cmdlets for ADFS use parameters typed as System.Security.Cryptography.X509Certificates.X509Certificate2. So the parameters expect that you will be passing an object rather than the common name string. When I first saw this in the API reference, I started trying to translate .NET code over for pulling a certificate from the stores like in the code on this article: http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.aspx.

Translating the code from C# to PowerShell was very difficult and somewhat error prone. Eventually I gave up and looked for a better option. I finally found out that the certificate stores load as a PowerShell drive in PowerShell 2.0 and the certificates surface as X509Certificate2 objects. Here is an overview article on the certificate provider: http://technet.microsoft.com/en-us/library/dd347615.aspx. The certificate provider makes it very easy to reference a certificate when working with the ADFS API.

I was moving over claim issuers and relying parties that had mapped claim rules attached to them. The basic approach for handling this was to use the “Get-” cmdlet to output the claim rules, copy the rules to a text file, and then run the “Set-X-RulesFile” to reimport the rules. This is easier to understand when seen in an example.

  1. First I am going to get all of the claim issuers output to the PowerShell window:
    Get-ADFSClaimsProviderTrust
    

    This outputs something like this:

    AcceptanceTransformRules : @RuleTemplate = “PassThroughClaims”
    @RuleName = “Pass thru role”
    c:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/role”%5D
    => issue(claim = c);@RuleTemplate = “PassThroughClaims”
    @RuleName = “Pass Thru Name”
    c:[Type == “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name”%5D
    => issue(claim = c);

    I just take everything after the colon on the first line and copy to a text file called acceptanceTransformRules.txt.

  2. Finally, I am going to import a claims issuer’s rules (the claim issuer MySTS needs to already exist at this point:
    # Refer to the rules file with an absolute path
    $curdir = Get-Location
    $curdirfile = Join-Path -Path $curdir -ChildPath "acceptanceTransformRules.txt"
    
    # Import the claim rules
    Set-ADFSClaimsProviderTrust -TargetName "MySTS" -AcceptanceTransformRulesFile $curdirfile
    

Moving a claims issuer from one server to another is resolved to two basic steps: 1. Installing the certificates, 2. Running a script referencing the installed certificates. Here is a PowerShell example combining the ADFS and Certificate APIs for creating a new issuer:


Write-Host "Loading ADFS Snap-In"
Add-PSSnapin Microsoft.ADFS.PowerShell

$certname = "CN=localhost"

# Getting the certificate. How simple using the certificate provider!
set-location cert:\localmachine\my
$cert = Get-ChildItem | Where { $_.Subject -eq $certname }

Set-Location c:

# certificates are correctly specified here
Add-ADFSClaimsProviderTrust -Name "MySTS" -Identifier "https://localhost/trust" -MonitoringEnabled $false -TokenSigningCertificate $cert -EncryptionCertificate $cert -AutoUpdateEnabled $false -AllowCreate $True

Write-Host "Disabling CRL checking on claims issuer trust certs because these are self-signed"
Set-ADFSClaimsProviderTrust -TargetName "MySTS" -SigningCertificateRevocationCheck "None"
Set-ADFSClaimsProviderTrust -TargetName "MySTS" -EncryptionCertificateRevocationCheck "None"

Conclusion

After going through the process of creating scripts like this I realized it is not too much work to do a scripted move but it would be much nicer if you could just export to script from the ADFS mmc or execute some other cmdlet to handle this whole process for you. Maybe in the future I might try to spend some time making something like this and putting it on CodePlex. Thanks!

Getting claims to map in an R-STS scenario with ADFS 2.0

Introduction

If you have been following along, you know I have been playing with the R-STS scenario quite a bit lately. I have many, many more posts to do on this topic and I am working on these so stay tuned. Tonight I worked through a couple of hurdles in my R-STS and finally got the claims to map through ADFS 2.0. For my R-STS, I am using Dominick Baier’s StarterSTS (http://startersts.codeplex.com/) with a custom authentication provider (using a SQL user store) and then adding on some custom claims. Then I pass the resulting token into ADFS 2.0 and handle some custom claim mapping and a reissued SAML token with the final set of claims. For a long time I had been struggling to get ADFS to handle the custom claim mapping. Finally I figured out what was happening. In this post I just want to give some more information.

Details

In ADFS you can configure claim issuer trusts and relying party trusts. When you are doing an R-STS scenario, you end up with at least 2 claim issuers and at least 1 relying party. Most of the content out there is on passive federation and single STS scenarios; there is not as much information on active federation or R-STS scenarios. This makes everything more complicated. Understanding the claim procesing flow is the key. I finally started digging into some of the claim pipeline like in this article: http://technet.microsoft.com/en-us/library/ee913585(WS.10).aspx. But again, this is really more about an STS scenario rather than an R-STS scenario.

So reading this article it is easy to see that the processing order for an STS:

  1. Claims arrive at your STS party (Idp) which is ADFS
  2. Then are mapped
  3. Then sent onto the relying party in ADFS specified in your RST AppliesTo
  4. Then they are mapped
  5. Then sent back in the security token back from ADFS.

In R-STS, actually what happens is:

  1. Claims arrive at the original STS party (Idp) which in my case is the StarterSTS claim issuer
  2. Then are mapped
  3. Then sent onto the relying party in ADFS specified in your RST AppliesTo (but for the R-STS security token request)
  4. Then they are mapped
  5. Then sent back in the security token back from ADFS

I had been struggling with understanding the execution order. I had been trying to put the claim rules on the built-in “Active Directory” claim issuer because this was leg 2 of my R-STS scenario but ADFS is smart enough to pass these claims right through the built-in claim issuer. You still need to configure a pass through claim rule on both the original STS claim issuer and the relying party but everything flows through as expected.

Some hints that helped me today to accomplish this are:

– Output the claims after every token is returned. This helps you to start thinking about the pipeline.
– Start out with a claim rule in ADFS that does not evaluate incoming claims conditionally. So for example, try a hard coded claim rule like this (from http://technet.microsoft.com/en-us/library/dd807118(WS.10).aspx):

=> issue(type = “http://test/role“, value = “employee”);

The rule above just adds a hard coded claim to the resulting claims. Another helpful tip is that the rule above can issue any claim type even ones not documented in the claim descriptions of ADFS. So you can basically inject claims here without registering them with ADFS. This is both a nice flexibility and a challenge because this can hurt your debugging.

But if you are just trying to figure out where claims get injected into the pipeline, add the rule above to your relying party to just get a claim to show up on the returned token.

– Keep all the built-in Active Directory claim issuer rules in place because these are good examples. Also review the rule syntax for the rules to get a better understanding of what is happening.

Let me know if you have any questions and I will keep posting some lessons learned on my adventures with ADFS!

Thanks,

Enabling configuration for ADFS 2.0 Server Traces

Introduction
I was working on doing diagnostics for an R-STS scenario with ADFS 2.0 and I was getting lots of errors. I had enabled tracing already for the client side but I was not getting any error traces for the client. I think a large percentage of the errors occur on the ADFS server side. So I enabled tracing for the server side as well. I just tried it and did not know if this was officially supported or documented. Doing this REALLY helped me figure out the problem and deal with error messages that were confusing. In this post I am going to show what I did to enable the server side tracing.
In doing secondary research for this post I did see this post from back during ADFS Beta 2: http://imav8n.wordpress.com/2009/08/06/enabling-logging-in-adfs/. I will just update some of the details here.
Details
The config for ADFS is found in c:\Program Files\Active Directory Federation Services 2.0 called Microsoft.IdentityServer.ServiceHost.exe.config. Here is a copy of my config file with the tracing added. You should be able to copy this directly and then the tracing can be output for you. I did also disable the default trace ADFS listener because I wanted the output to go to a file so that I can correlate everthing using the service trace viewer
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <section name="microsoft.identityServer.service" type="Microsoft.IdentityServer.Service.Configuration.ServiceConfiguration, Microsoft.IdentityServer.Service, Version=6.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
    <section name="microsoft.identityServer" type="Microsoft.IdentityServer.Service.Configuration.IdentityServerConfiguration, Microsoft.IdentityServer.Service, Version=6.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"/>
    <section name="microsoft.identityServer.proxy" type="Microsoft.IdentityServer.Service.Configuration.ProxyConfigurationSection, Microsoft.IdentityServer.Service, Version=6.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"/>
  </configSections>

  <microsoft.identityServer serviceMode="Server"/>

  <microsoft.identityServer.proxy >
    <host name="" httpPort="80" httpsPort="443" />
    <proxyTrust proxyTrustRenewPeriod="240" />
  </microsoft.identityServer.proxy>

  <microsoft.identityServer.service>
    <policyStore connectionString="Data Source=\\.\pipe\mssql$microsoft##ssee\sql\query;Initial Catalog=AdfsConfiguration;Integrated Security=True"
      administrationUrl="net.tcp://localhost:1500/policy" />
    <trustMonitoring enabled="true" />

  </microsoft.identityServer.service>
  <system.diagnostics>
    <sources>
      <!-- To enable WIF tracing, change the switchValue below to desired trace level - Verbose, Information, Warning, Error, Critical -->
      <!-- Set TraceOutputOptions as comma separated value of the following; ProcessId ThreadId CallStack. Specify None to not include any of the optional data-->
      <!-- NOTE THAT THE CHANGES TO THIS SECTION REQUIRES SERVICE RESTART TO TAKE EFFECT -->
      <source name="Microsoft.IdentityModel" switchValue="Verbose">
        <listeners>
<!--          <add name="ADFSWifListener"  traceOutputOptions="ProcessId,ThreadId" initializeData="Wif" type="Microsoft.IdentityServer.Diagnostics.ADFSTraceListener,Microsoft.IdentityServer,Version=6.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> -->
          <add name="ADFSWifListener" />

        </listeners>
      </source>

      <!-- To enable WCF tracing, change the switchValue below to desired trace level - Verbose, Information, Warning, Error, Critical and
           uncomment the system.servicemodel section below -->
      <source name="System.ServiceModel" switchValue="Verbose" >
        <listeners>
        <!--  <add  name="ADFSWcfListener" traceOutputOptions="ProcessId,ThreadId" initializeData="Wcf" type="Microsoft.IdentityServer.Diagnostics.ADFSTraceListener,Microsoft.IdentityServer,Version=6.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> -->
          <add name="ADFSWcfListener" />
        </listeners>
      </source>
      <source name="System.ServiceModel.MessageLogging" switchValue="Verbose" >
        <listeners>
<!--          <add  name="ADFSWcfListener" traceOutputOptions="ProcessId,ThreadId" initializeData="Wcf" type="Microsoft.IdentityServer.Diagnostics.ADFSTraceListener,Microsoft.IdentityServer,Version=6.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> -->
          <add name="ADFSWcfListener" />
        </listeners>
      </source>
    </sources>
    <!-- Added by Ben Cline - sharedListeners -->
    <sharedListeners>
      <add name="ADFSWcfListener"
           type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
           initializeData="c:\temp\adfssvchost_servicemodel.svclog"
           traceOutputOptions="Timestamp">
        <filter type="" />
      </add>
      <add name="ADFSWifListener"
           type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
           initializeData="c:\temp\adfssvchost_wif.svclog"
           traceOutputOptions="Timestamp">
        <filter type="" />
      </add>

    </sharedListeners>
    <trace autoflush="true" ></trace>

  </system.diagnostics>

 <system.serviceModel>
    <diagnostics>
      <messageLogging logEntireMessage="true"
              logMessagesAtServiceLevel="true"
              logMessagesAtTransportLevel="true">
      </messageLogging>
    </diagnostics>
  </system.serviceModel>

  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

Some tips on Active Federation with ADFS 2.0

Introduction

At work I have been working on helping my company adopt ADFS 2.0 for a Single Sign-On initiative. I have been working on handling a WPF application’s federation with ADFS and needed to use active federation (by definition). I had been following along with Dominick Baier’s blog post at http://www.leastprivilege.com/WIFADFS2AndWCFndashPart5ServiceClientMoreFlexibilityWithWSTrustChannelFactory.aspx. Dominick’s sample seemed simple enough but there were quite a few ADFS things I had to figure out in order to make things work.

BTW, learning how WIF relates to ADFS has been quite challenging but I have found the following article to be very helpful: http://technet.microsoft.com/en-us/library/adfs2-federation-wif-application-step-by-step-guide(WS.10).aspx.

Issues

    1. So I downloaded the code for the post and tried running the ManualClient. This uses WSTrustChannelFactory to connect to ADFS. The base sample uses the following ADFS endpoint:
      static string stsEndpoint = "https://<machine.domain>/adfs/services/trust/13/windowstransport";
      

      The windowstransport ADFS endpoints are not enabled by default so you must enable them. When I first tried to enable the WS Trust 1.3 version it would enable but it would not allow me to enable the proxy for this. I am not sure why. Eventually I was able to get the WS Trust 1.3 endpoint enabled. So at first I switched over to using the WS Trust 2005 endpoint. I also had to change the trust version to TrustVersion.WSTrustFeb2005 in the code as well. Here is the updated code at this point:

      static string stsEndpoint = "https://<machine.domain>/adfs/services/trust/2005/windowstransport";
      static string svcEndpoint = "https://<machine.domain>/soapadfs/service.svc";
      
      static void Main(string[] args)
      {
         var token = GetToken();
         CallService(token);
      }
      
      private static SecurityToken GetToken()
      {
         var factory = new WSTrustChannelFactory(
            new WindowsWSTrustBinding(SecurityMode.Transport),
            stsEndpoint);
      
         factory.TrustVersion = TrustVersion.WSTrustFeb2005;
      
         var rst = new RequestSecurityToken
         {
            RequestType = RequestTypes.Issue,
            AppliesTo = new EndpointAddress(svcEndpoint),
            KeyType = KeyTypes.Symmetric
         };
      
         var channel = factory.CreateChannel();
      
         return channel.Issue(rst);
      }
      
    2. So then I ran the code and it got all the way to the last line of the method GetToken() and it gives me the following nearly meaningless message back (wrapped in a FaultException):

      ID3082: The request scope is not valid or is unsupported.

      I was not able to find very good information on this error but I eventually found out that one of the reasons that this can occur is if you have not setup a relying party trust in ADFS for your application.

    3. Like with much of active federation, there is a lot less documentation out there for active federation than for passive federation. Here are the steps I went through to create an ADFS relying party for an application that does not have a federation metadata document:

      a. In the ADFS 2.0 MMC, click to create a Relying Party trust.
      b. On the next page of the wizard, choose to “Enter data about the relying party manually”. Click Next
      c. Enter a display name, click Next
      d. Choose ADFS 2.0, click Next
      e. For now, do not specify an encryption certificate, click Next,
      f. Do not click to enable WS-Federation passive or Web-SSO. Both of these are browser-based options that do not apply to active federation. Click Next.
      g. For the relying party trust identifier, enter the endpoint reference you are using to call over WS-Trust, so I entered: <a href=”https:///soapadfs/service.svc”>https://<machine.domain>/soapadfs/service.svc
      h. Click to allow all to access the relying party, click Next.
      i. Click Next to move on to the Claims Authorization Wizard.
      j. I just specify a Windows username pass through at this point (see the link in my introduction for how to set this up).

    4. So then I try to run the application again and I get another annoying error message:

ID3037: The specified request failed.

Unfortunately, this is a very general one. The solution I found was to go into the relying party trust and specify the encryption certificate.

  1. Then I can successfully get the GetToken method to work properly. I did also notice one other variation in the ADFS configuration that can lead to a weird error. If you disable a relying party trust and try to run the application again you get the following error:

MSIS3127: The specified request failed.

To resolve this, simply enable the relying party trust.

Conclusion

I have found that much of the work with ADFS goes by the principle, “no guts, no glory,” and I wish it were easier. There seem to be a large number of errors that you can receive without a lot of helpful information for helping you resolve them. Hopefully these error resolutions will help you and I will be sure to post any other workarounds I find as I get further down the road of implementing ADFS.

Thanks,

Blog at WordPress.com.

Up ↑