Wednesday, April 12, 2017

Visual Studio : ReplacableToken_ApplicationServices on transform

Using VS 2015 and doing a transform on my web.config.

My connection string is something like:

connectionString="data source=.\SQLEXPRESS;Integrated Security ..."

but what appears in the transformed file is:

connectionString="$(ReplacableToken_ApplicationServices-Web.config Connection String_0) ..."


Much conversation with Mr. Google and eventually the solution is:


You need to add this to the appropriate .pubxml file under Properties\PublishProfiles


Thursday, April 06, 2017

C# : Sending email

I needed a quick and dirty C# module to test a SMTP server.

So I came up with this.

I used Windows Forms and a very simple button.

This uses the System.Net.Mail.MailMessage class.


ASP.NET : Comparing a VS project to a web site

We have a legacy project which has been untouched and unloved for many years.

Then we needed to make an urgent change.

The problem was that the people who had worked on the project were long gone and nobody knew if the source in the repository was up to date?

So we took a copy of the web site,

You can't just compare this to the VS project. In VS, you have an aspx file and a corresponding cs file. In the web site, the aspx files are there but the cs files are all rolled up into a dll.

You can't directly compare dll either. There's guids, dates etc. that keep changing every time you do a build.

The way to do this is to deploy your current project to IIS and then compare the two web sites.

You still have the problem of comparing the dll files in the \bin directory.

To do this, decompile the dll into a VS project using something like Reflector or dotPeek and then compare the source code.

Some of the names will be different e.g. "isComplete" vs. "flag1" but it will give you a pretty good idea of whether there have been code changes,


Thursday, March 30, 2017

WCF : Calling an async. method

Just for a change, I was asked to help out on a non-Identity project.

There was a legacy host that only understood SOAP and a modern back-end that only supported REST web API.

So we need a bridge between them and I had to remember everything I had ever forgotten about WCF.

I needed my WCF method to call:

string response = await my_api.CallREST(parameter);

The problem is that the compiler expects the method to be decorated with async.

Aync. WCF?

Turns out you can.

My method looks like:

public async Task<validateresponse> ValidateParameter (parameter)

... and similarly for the interface.

And it works!

The word "async" doesn't appear anywhere is the WSDL. It appears to be completely ignored.


Friday, March 24, 2017

ADFS : Copy claims rules over

Don't know how many times I've done this.

Deploy to Dev., get everything working, deploy to QAS, support QAS acceptance testing, deploy to Prod., smoke test.

When you have a lot of claims rules to copy over, I found a neat way to do it.

e.g. for a CP.

(Get-AdfsClaimsProviderTrust -Name "My CP").AcceptanceTransformRules | Out-File “C:\path\CPClaimsRules.txt”

And then import the rules to the new CP.

Set-AdfsClaimsProviderTrust -TargetName "My CP" -AcceptanceTransformRulesFile “C:\path\CPClaimsRules.txt” .

I've found that this also avoids the issue where you keep a copy of the rules in e.g. Word and then when you try and paste them into the ADFS wizard, you get all kinds of format errors.


Wednesday, March 22, 2017

ADFS : Creating a custom attribute store

This is for ADFS 4.0 on Server 2016.

This is a good write-up.

Unfortunately, all the code is in a screen shot which sucks. Somewhat difficult to copy / paste :-).

Luckily, similar code can be found here.

Just standardise the names; one is "ToUpper"; the other is "toUpper".

However, my requirement was for getting claims from a back-end (details unimportant for the purposes of this post) where a user could have many claims of that type returned. Think of a property ID where one person owns a house but an investor owns several.

All the examples were for returning one attribute.

Essentially, you are returning a C# jagged array e.g. string[][] resultData.

My query string was :

c:[Type == ""]
 => issue(store = "CAS", types = ("HouseID"), query = "House", param = c.Value);

So I assumed that the multiple claims would be in the same row i.e. one row; many columns.

The search to the back-end returned 3 houses.

I then got:

Microsoft.IdentityServer.ClaimsPolicy.Language.PolicyEvaluationException: POLICY0019: Query 'House' to attribute store 'CAS' returned an unexpected number of fields: expected '1', got '3'.

If you look at the query, you'll see there is only one query parameter which will only return one result. A query which returns 3 results would be like:

query = "House", sn, mail

So what I need is 3 rows; one column.

I found some guidance around this here.

One of the things that stumped me for a while was the fact that the array had to be dynamic because there could be any number of houses. That's why you add to a list and then cast to an array.

 Another gotcha was the fact that while you can have a static rule like:

=> issue(type = "HouseID", value = "123456");

you cannot have that in a query string for the attribute store. You get:

System.ArgumentException: ID4216: The ClaimType 'HouseID' must be of format 'namespace'/'name'.

So it needs to be:

c:[Type == ""]
 => issue(store = "CAS", types = ("http://claim/HouseID"), query = "House", param = c.Value);

i.e. HouseID becomes http://claim/HouseID.

Once you have compiled a new .dll file, you have to copy it over in the ADFS directory on the server.

You will get "Access Denied" because ADFS is running. So you have to stop the ADFS service, copy over the .dll and then start up the service again.

You do not have to delete the custom attribute store in the wizard and reload it. When ADFS starts. it will load the latest .dll.

The gist with the code is here.

But if you want a sneak preview, the important part is:

    // Dummy values to illustrate the principle.

    List<string> claimValues = new List<string>();
    List<string[]> claimData = new List<string[]>();

    // Each claim value is added to its own string array 
    foreach (string claimVal in claimValues)
        claimData.Add(new string[1] { claimVal });

    // The claim value string arrays are added to the string [][] that is 
    // returned by the Custom Attribute Store EndExecuteQuery()
    string[][] resultData = claimData.ToArray();

    TypedAsyncResult<string[][]> asyncResult = new TypedAsyncResult<string[]
    []>(callback, state);
    asyncResult.Complete(resultData, true);
    return asyncResult;

catch (Exception ex)
    String innerMess = "";
    if (ex.InnerException != null)
        innerMess = ex.InnerException.ToString();
    throw new AttributeStoreQueryExecutionException("CAS exception : " +
       ex.Message + " " + innerMess);


Tuesday, March 21, 2017

NLOG : Logging for both web site and web API

I have a standard .NET 4.5 MVC web site with web API.

I added NLOG to the web API via the NuGet packages and log away quite happily.

Part of the NuGet are the files:
  • NLog.config
  • NLog.xsd
I then wanted to do logging in the web site. So add the NuGet to that. Added quite happily but no NLog files?

So I copied the two NLog files from the web API project and pasted them into the web site project.

I changed the logging file name. So I have web-site.log and web-api.log.

Works fine.


Monday, March 20, 2017

ADFS : WIF10201: No valid key mapping found

The error is:

WIF10201: No valid key mapping found for securityToken: 'System.IdentityModel.Tokens.X509SecurityToken' and issuer: 'http://MY-ADFS/adfs/services/trust'.

I have a simple WIF application circa VS 2012 that I use to display claims and ported it over to use on ADFS 4.0.

Then I got the above error.

The solution is as per Signing key rollover in Azure Active Directory.

Yes - it says AAD but the client-side code for ADFS is the same since it's all driven from the metadata.

Use the code from: "Web applications protecting resources and created with Visual Studio 2012".

When I compared the web.config changes, the error seemed to be because the server name is "MY-ADFS" (in caps) but I had written "my-adfs" (no caps) in the web.config.

The thumbprint was also in caps. (Although I've never had an issue with that).

It gives you a nice comment:

"Element below commented by: ValidatingIssuerNameRegistry.WriteToConfg on: '20/03/2017 1:00:16 a.m. (UTC)'. Differences were found in the Metadata from: ..."


AAD : Beware the difference in the V1.0 and V2.0 endpoints

The V2.0 endpoint is the endpoint that allows you to sign in with the converged Microsoft and Azure Active Directory accounts.

From an OpenID / OAuth perspective, the discovery documents can be found at:



Notice the extra "V2.0".

If we look at the keys, we see for V1.0:

And for V2.0 :

You will notice that V2.0 has four signing keys while v1.0 only has two.

V2.0 is the endpoint used by B2C.

Don't assume that tokens signed by Azure AD (V1.0) are also acceptable for B2C (V2.0) and vice versa.

This will be true when AAD and B2C are merged at some point in the future but right now it's a gotcha!


Wednesday, March 15, 2017

ADFS : Using the client_credentials flow with ADFS 4.0 returns 401

Been battling with this for ages. I'm using ADFS 4.0 on Server 2016.

My web site uses OpenID Connect and that uses the OWIN authorisation code grant.

When I use the authorisation code grant,  I can get the code, exchange the code for an access token and then call a web API with that access token no problem.

But that flow requires a user to authenticate and for some of my use cases there is no user. An example would be a forgotten password flow where the user cannot authenticate. To do that, I use the client_credentials flow.

Getting this to work was a non-trivial task since the documentation is (shall we say) sub optimal.

I also needed the ADAL Nuget package. Interesting that you can mix OWIN and ADAL

So under "Application Groups", I have the "Server application accessing a web API" scenario.

The server application has a client ID and the secret key.

So assume the general API URL is:


And the API's inside this are like:


The web API in the wizard has the RP identifier:


(This matches the ValidAudience below).

Access control policy is:

Permit everyone

I have one custom claim rule:

c:[] => issue(claim = c);

Now there is no way to pass these claims back in the JWT but I added this during my troubleshooting.

Client permissions is set to:

"All clients"

with scope of:

openid and user_impersonation.

The web API controller is decorated with  [Authorize].

The code is:

using Microsoft.IdentityModel.Clients.ActiveDirectory;

ClientCredential clientCredential = new ClientCredential(clientId, secretKey);

AuthenticationContext ac = new AuthenticationContext("https://my-adfs/adfs/", false);
AuthenticationResult result = await ac.AcquireTokenAsync("https://my-pc/WebService"

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,  
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string,  
string>("foo", "blah"), new KeyValuePair<string, string>("foo1", "blah1") });
request.Content = content;
HttpResponseMessage response = await client.SendAsync(request);

if (response.IsSuccessStatusCode)
    // etc

To call a second API, it's the same code with one change:

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://my-pc/WebService/api/my-api1/");

I also has to change to code for the web API App_Start, Startup.Auth.cs.

    new ActiveDirectoryFederationServicesBearerAuthenticationOptions
        TokenValidationParameters = new TokenValidationParameters()
            SaveSigninToken = true,
            ValidAudience = "https://my-pc/WebService"
        MetadataEndpoint = "https://my-adfs/FederationMetadata/2007-06/FederationMetadata.xml"


Monday, March 06, 2017

ADFS : Health Check

This is a question I've been asked a number of times.

Usually, you just ping the metadata endpoint or the IDPInitiatedSignOn endpoint.

Then I found: AD FS Diagnostics Module.

"The AD FS Diagnostics Module contains commandlets to gather configuration information of an AD FS server, as well as commandlets to perform health checks to detect configuration issues based on common root causes identified during support engagements such as duplicate SPN, cert".

There are some other useful links on the LHS.

On my list to try out :-)


Tuesday, February 14, 2017

AD : Using PowerShell to create users and groups

ADFS specifically targets authentication and authorisation.

It does not target provisioning users into AD or adding them to groups. You need an Identity Manager for that.

You can do this with PowerShell and there are many links to do this around on the Internet e.g. PowerShell: Bulk create AD Users from CSV file.

My version

csv file format is:



$Users = Import-Csv -Path "C:\blah\Users.csv"            
foreach ($User in $Users)            
    $Displayname = $User.'Firstname' + " " + $User.'Lastname'            
    $UserFirstname = $User.'Firstname'            
    $UserLastname = $User.'Lastname'            
    $OU = $User.'OU'            
    $SAM = $User.'SAM'            
    $UPN = $User.'Firstname' + "." + $User.'Lastname' + "@" + $User.'Maildomain'            
    $Password = $User.'Password'  
    $Email = $User.'Email'
    New-ADUser -Name "$Displayname" -DisplayName "$Displayname" -SamAccountName $SAM -UserPrincipalName $UPN -GivenName "$UserFirstname" -Surname "$UserLastname" -Description "$Description" -AccountPassword (ConvertTo-SecureString $Password -AsPlainText -Force) -EmailAddress "$Email" -Enabled $true -Path "$OU" -ChangePasswordAtLogon $false –PasswordNeverExpires $true -server mydomain.local 

    Write-Host –NoNewLine "Adding user:  "
    Write-Host $SAM
$Users = Import-Csv -Path "C:\blah\Users.csv"            
foreach ($User in $Users)            
    $SAM = $User.'SAM'            
    Add-ADGroupMember -Identity "My Group 1" -Member $SAM
    Add-ADGroupMember -Identity "My Group 2" -Member $SAM
    Write-Host –NoNewLine "Adding groups for user:  "
    Write-Host $SAM


Wednesday, February 08, 2017

ADFS : Useful PowerShell cmdlets

I seem to be running these on just about every installation I do these days so thought it would be worthwhile to note them.

Login with email address

This seems to be more and more common.

Set-AdfsClaimsProviderTrust -TargetIdentifier "AD AUTHORITY" -AlternateLoginID mail -LookupForests 

There are some gotchas with this especially if you are thinking of extending out to Azure (via AD Connect) at some point.

Configuring Alternate Login ID

Certificate revocation

Most Dev. instances don't have access to the extranet. The ADFS login will be slower because ADFS will try and check for certificate revocation.

So it makes sense to remove this functionality.

Set-AdfsRelyingPartyTrust -TargetIdentifier urn:xxx -SigningCertificateRevocationCheck None -EncryptionCertificateRevocationCheck None


Although the time across servers should be consistent, in a lot of cases it isn't. This means that if the ADFS server is ahead, the SAML token will be in the future and the SAML RP will reject it.

Some SAML RP that I have dealt with have the skew hard coded so it cannot be altered.

The best solution is to ensure that the server time is synchronised but if that is not possible, you can "back date" the time in the token. The cmdlet below sets the time 3 minutes backwards.

Set-AdfsRelyingPartyTrust -TargetIdentifier urn:xxx:de -NotBeforeSkew 3

ADFS Not Before Time Skew 


Wednesday, January 18, 2017

WebAPI : Using Swagger and Postman with multiple POST

I've been busy on a WebAPI project where I need multiple POST actions in a a WebAPI controller.

There's a ton of stuff around best practice for this - some say each controller should only have one POST; others disagree.

I finally decided to break the controllers up by category and then within each controller have multiple POST functions.

I also added Swagger via Swashbuckle and used Postman for some unit tests so I thought I would write this all up.

As an example, I have a Maths controller with Add and Multiply.

The controller:

using MathsWebAPI.Models;
using System.Web.Http;

namespace MathsWebAPI.Controllers
    public class MathsController : ApiController
        // POST: api/Maths
        public int Add([FromBody] Maths info)
            int number1 = info.number1;
            int number2 = info.number2;

            return (number1 + number2);

        // POST: api/Maths
        public int Multiply([FromBody] Maths info)
            int number1 = info.number1;
            int number2 = info.number2;

            return (number1 * number2);

Note that I am passing in a model object as I have multiple parameters.

The model object:

namespace MathsWebAPI.Models
    public class Maths
        public int number1 { get; set; }

        public int number2 { get; set; }

The WebAPIConfig.cs:

using System.Web.Http;

namespace MathsWebAPI
    public static class WebApiConfig
        public static void Register(HttpConfiguration config)
            // Web API configuration and services

            // Web API routes

                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }

                name: "ControllerAndAction",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new {id = RouteParameter.Optional}

In SwaggerConfig.cs, uncomment:


to avoid the error message.

Now if I navigate to:


where 44571 is my IIS Express port

I see:

Now if you click the yellow area, it will populate the "Info" box with the model object format and you just have to fill in the values.

Click "Try it out" and you see:

 Note the URL is:


Now if we add Postman, the commands are in this gist.

Running the script, we see:


Friday, January 13, 2017

ASP : Classic ASP and ADFS

This question comes up from time to time and there was a recent one on the forum.

I did this back in the day so thought it would be a good idea to write up.

Classic ASP is basically just static HTML files, no web.config, no ASP pipeline, no code behind etc.

And they are still out there.

So we will integrate it with ADFS using WIF i.e. everything is driven by a web.config.

Which means we need to add one - example in this gist.

To test this, I used a Windows 8 PC.

This PC has both WIF 3.5 and WIF 4.5 installed. WIF 4.5 is built into ASP.NET 4.5.

So you may need to install this.

This is the line in the web.config:

section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

Also WIF doesn't run on Server 2003 - hopefully you've upgraded by now :-)

The key to getting WIF to be added to the pipeline is this line:

modules runAllManagedModulesForAllRequests="true"

Note that the logging at the end is optional and this part of the file can be removed if not required.

All the above has to be done "on the fly". There is no way to create a Classic ASP project in Visual Studio. (In fact, Microsoft go out of their way to actively discourage this. The official line is that Classic ASP is no longer supported). Consequently, there is no context checking, intellisense or compile / run phase.

I didn't use VS at all - just Notepad++.

Also remember to enable ASP under Windows Features / IIS / World Wide Web Services / Application Development Features.

And you can't use an classic Application Pool in IIS - it needs to be integrated.

I just took a folder on my local PC and added a Default.asp (which contained some basic HTML) and the web.config.

Then I added this as an application in IIS. I called the application "ClassicASPBasic".

I then configured an RP on ADFS. There is no metadata so it easiest to do this manually.

Then configure the web.config. You just need to alter the "my-adfs" to your ADFS FQDN and "my-pc" to wherever you have the website hosted.

Also remember to update the thumbprint with your ADFS token-signing certificate.

Then in IIS on the RHS - "Browse Application" - use the port 443 link.

IIS sees the web.config. The web.config tells it to use the WIF classes. WIF by default protects everything so it sends you off to ADFS to authenticate. Authenticate and you are redirected back to the simple .asp page.

The next step is to Sign Out. You cannot use the standard WIF FederatedSignOut messages because there is no .NET framework so it has to be done via a URL link on the page. If you want the user to end up on the ADFS logout page, use:

&lt;a href="https://my-adfs/adfs/ls/?wa=wsignout1.0">Sign out&lt;/a>

If you want to the user to be redirected back to the application use:

&lt;a href="https://my-adfs/adfs/ls/?wa=wsignout1.0&wreply=https://xxx/yyy/Logout.asp">Sign out and return&lt;/a>

  • xxx is the ADFS URL
  • yyy is the application URL
  • Logout.asp is a new page added to the site

The other way is to use VS and create an MVC application. Refer How To: Build Claims-Aware ASP.NET MVC Web Application Using WIF.

Then simply copy all your static files over.

This way allows you to access the claims as per the link. 


Monday, January 09, 2017


I was helping a customer who kept getting:


when trying to change passwords programmatically.

I referred him to the password rules in the default policy i.e. the usual:

Passwords may not contain the user's samAccountName (Account Name) value or entire displayName (Full Name value). Both checks are not case sensitive.

The password contains characters from three of the following categories:
  • Uppercase letters of European languages (A through Z, with diacritic marks, Greek and Cyrillic characters)
  • Lowercase letters of European languages (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
  • Base 10 digits (0 through 9)
  • Non-alphanumeric characters (special characters) (for example, !, $, #, %)
The customer was adamant that the password conformed to these.

But there is also another constraint - by default you cannot change your password more than once a day.

This was the reason that was tripping the customer up.

The moral of the story is that if the error says "Password constraint" it's because there is one :-)


You may be wondering how to handle the case where you register a new user programmatically. Normally you create a temporary password and send this in an email and the user clicks on a link which takes them to some kind of "Change Password" page.

The user then changes their password.

In this case, the password is being changed twice so how do you get around the constraint?

Add the "User must change the password at next logon" flag when you create the temporary password.