Thursday, June 22, 2017

ADFS : Moving the "Active Directory" IDP entry to the top of the list

This is for ADFS 4.0 (Server 2016).

This post shows how to rename the "Active Directory" IDP and at the bottom of the post is a comment around "move Active Directory to Top" and some script.

This script assumes a set number of IDP and looking at the script, it seems to move the names but not the URL i.e. if you had two IDP viz. IDP A and "Active Directory", it would swop the names around but clicking "Active Directory" would in fact navigate to IDP A.

Also, there are other considerations as mentioned by @Jorge in a reply:

"The list of IdPs in general is dynamic as you may add or remove an IdP. The code should take that into account.

In addition, per RP trust you can also specify a list of IdP that are allowed to use that RP trust and that list is a subset of the overall list of IdPs. Again, the code should take that into account.

It should not matter how long the IdP list is.

Is it possible to make this dynamic where the logic is something like:

* if more than 1 IdP is listed and the AD IdP is included, then move that IdP to the top."

I needed to move this entry to the top.

I would not have got this to work without having the ability to debug.

This is the script I came up with:

// This script moves the "Active Directory" entry (the local IDP) to the top of the list.

// Per RP trust you can specify a list of IdP that are allowed to use that RP trust and that list is a subset 
// of the overall list of IdPs. This list may not have an "Active Directory" entry.

// If there is only one entry, no point in re-ordering!

// The logic is:

// If more than one IDP is listed and the "Active Directory" IDP is included, then move that IdP to the top.

//debugger;

var idp = document.getElementsByClassName("idp");

var totElements = idp.length;

var listAllSpanForIdp = document.getElementsByClassName("idpDescription float");

var adElementPresent = false;

var inc;

for (inc = 0; inc < listAllSpanForIdp.length; inc++) {
  if (listAllSpanForIdp[inc].innerText == "Active Directory") {
    adElementPresent = true;
  }
}

if ((totElements > 1) && (adElementPresent)) {
  var lastElement = totElements - 1;

  idp[lastElement].parentNode.insertBefore(idp[lastElement], idp[0]);
}

I move the elements around in the DOM as per this.

idp[lastElement].parentNode.insertBefore(idp[lastElement], idp[0]);

This inserts one node before the other.

You get the parent of the node. You then call the insertBefore method on the parent and you pass it the idp[lastElement] node (the "Active Directory" IDP  one) and tell it you want it inserted before idp[0] (the first one). This then swops their order.

This seems to work across all the requirements.

Enjoy!

Wednesday, June 21, 2017

ADFS : Debugging onload.js when customising the Login, Update Password and Home Realm Discovery (HRD) screens

This is for ADFS 3.0 / 4.0 (Server 2012 R2 and 2016).

I've done a number of posts around the customisation but the hard part is that I could not debug the JavaScript in onload.js.

Then I came across JSFiddle.

This allows you to input your HTML, your css and your JavaScript and then run the script and see the result.


The screen is divided into four segments:

Top left = HTML
Bottom left = JavaScript
Top right = css
Bottom right = Result

I'm playing around with the order of the HRD IDP so this post concentrates on that functionality.

(The full post around the order is here).

In a browser, navigate to the HRD screen and then view the source.

Copy the entire:

form id="hrd" method="post" autocomplete="off" ...

section into the HTML.

Obviously, what HTML you select depends on what you are trying to achieve.

Then write your JavaScript in the JavaScript window.

Then click "Run".


The results will show in the Results section.

Perfect - so now I can play around and see the result. This is far easier than going through the whole theme / upload etc. procedure.


What would be the cherry on top is if I could step through and debug it. And like Bob the Builder - "Yes you can!".

 Run JSFiddle in Chrome.

You can add:

console.log (idp.length);

commands if you want.

To hook into the debugger, add this command at the point that you want to invoke it.

debugger;

In Chrome, hit "F12" - the developer window.

Now hit "Run" in JSFiddle.

The debugger will be activated:


 Now you can step through using the commands top right and view Local etc.


 Any console output you are looking for is under the "Console" tab.


This is SO much easier.

Enjoy!

ADFS : Adding extra text and links to the Login and Update Password screens

This is for ADFS 3.0 / 4.0. (Server 2012 R2 / 2016).

There are PowerShell commands to change text but there is nothing to add text, links etc.

We use the js inject mechanism described here.

Assume we want extra links on both the login and Update Password screens but we only want the text on the Update Password page.

And we want the text in blue.

The trick is to find an element on the Update Password HTML that is not on the login page.

"cancelButton" fits the bill.

// Add link and text

if ( document.getElementById("footerPlaceholder") ) {
   if ( document.getElementById("cancelButton") ) {    
     var str = "<p>Some extra text - lots of words - blah blah blah<p>";
     var result = str.fontcolor("blue");    
   } else {
     var result = "";
   }     
   
   document.getElementById("footerPlaceholder").innerHTML = result + "<br/><p>Help? <A href='https://company/help'>Get help</A></p><br/><p>Repost an Issue? <A href='https://company/issues'>Report an Issue</A></p>";
}

The screens are then:



Note that the "Cancel" button in the "Update Password" screen has been removed as per this.

Enjoy!

ADFS : Remove the "Cancel" button from the Update Password screen

This is for ADFS 3.0 / 4.0 (Server 2012 R2 / 2016).

I don't actually get the "Cancel" button on this screen. It doesn't seem to do anything and just confuses people.


So why not remove it?

This is just another change to onload.js. (See this for how to make a new theme etc.).


// Remove Cancel button for "Update Password" screen

if ( document.getElementById("cancelButton") ) {
   document.getElementById("cancelButton").outerHTML = "";
}

Notice the user of "outerHTML" to remove the whole element.

Enjoy!

Tuesday, June 20, 2017

ADFS : Continuing the Login and Home Realm Discovery (HRD) and Change Password customisation adventure

I've posted a number of times on this topic and during my research came across a number of useful articles so I thought I would wrap them all up as a reference.

This is for Server 2012 / Server 2016 - (ADFS 3.0 and 4.0).

I found that most articles I came across simply regurgitate the information in the official articles. I just wonder what the point is?

The official Microsoft reference is:

AD FS user sign-in customisation

Add sign-in page description

 Home Realm Discovery Customisation

Update Password

Some code to allow the ADFS Cancel button on the Update Password page (Expired Password) to redirect back to the original page.

ADFS 3.0 Cancel Button Redirection and Password Change Link

or

Just remove the button:

ADFS : Remove the "Cancel" button from the Update Password screen

Handling Expired Passwords in AD FS 2012 R2

What's interesting about this article is that the custom js is in a separate file, it's added via the additional file resources and then "injected" i.e.

Set-AdfsGlobalWebContent –SignInPageDescriptionText "&lt;script type=""text/javascript"" src=""/adfs/portal/script/custom.js"">&lt;/script>"

Most examples for text strings simply replace the text with other text but as this shows, you can replace the text with JavaScript. You could use this technique for the other screens as well.

You can add text and links:

ADFS : Adding extra text and links to the Login and Update Password screens

Login page


Adding buttons instead of links:

Customize the ADFS authentication page with buttons!

Using sAMAccountName to login rather than User Principal Name (UPN) or using DOMAIN\username.

Using SAMAccountName to Login to ADFS in Windows Server 2012R2/2016

or:

Accept SAM-account name as a login format on the ADFS form-based password update page

Don't like the screen - just redo it!


How to “TOTALLY” customize your Home Realm Discovery Page in Windows Server 2012 R2 ADFS

Hiding others customers when using Office 365.

Customizing the AD FS 3.0 Sign-in Page Logo

You can add text and links:

ADFS : Adding extra text and links to the Login and Update Password screens

Hiding some of the RP in the IDPInitiated scenario:

How to Hide a Relying Party from AD FS 3.0 

Forcing a button click automatically on logout:

How do I customize the ADFS 3.0 logout page to force sign out?

HRD

Changing the "Active Directory" name in ADFS 4.0 to something more appropriate.

ADFS 2016 – Change the Active Directory claim provider display name in the Home Realm Discovery page

At the bottom of that article, there is some JavaScript to show how to move this entry (the local AD) to the top. Unfortunately, it is hard-coded for a certain number of IDP and needs to be more dynamic.

This is a more dynamic solution.

ADFS : Moving the "Active Directory" IDP entry to the top of the list

If you use the -OrganizationalAccountSuffix to associate a RP with an IDP, you get something like:


If you want to skip this, have a read of:

Customize the Home Realm Discovery page to ask for UPN right away

-OrganizationalAccountSuffix is out-of-the-box for ADFS 4.0.

To do this in ADFS 3.0:

Customizing the AD FS sign-in pages per relying party trust

If you want to change the thumbnail images:

Customizing the IDP images in the Home Realm Discovery page


You can add extra text to the pages:

ADFS : Adding messages to the ADFS login / HRD screens




If you are wondering about how to get the small text inside the "button", refer:

ADFS : Adding extra text to the HRD screen IDP description

Theme

The standard commands to change the default theme are in the official links at the top.

Note that you can change any collateral in the theme.

ADFS : You can change anything in the Theme structure

Bonus

"Unless you have saved all the PowerShell cmdLet you typed to create your custom theme in your pre-production environment, it is quite challenging to recreate the exact same webtheme on your production servers. It is easy to export a configuration with the Export-WebTheme cmdLet. But it does not give you the ability to re-import what you just exported. I wrote the following script to help with that:"

How to export an ADFS custom web theme and import it to another serverrt-an-adfs-custom-webtheme-and-import-it-to-another-server/

And you can debug the onload.js. Bargain!

ADFS : Debugging onload.js when customising the Login, Update Password and Home Realm Discovery (HRD) screens


I'll keep adding to this page as new examples come to light.

Enjoy!

Friday, June 16, 2017

ADFS : Adding messages to the ADFS login / HRD screens

This is for Server 2012 R2 / Server 2016.

There are a number of posts out there around this, mainly around changing the text of an element in onload.js e.g.

element.innerHTML = "Some text"

where that element already has a text element and you are simply overwriting it.

What I didn't realise is that you can do this with any element, even those that don't currently have text.

I needed to add some text to the HRD screen. The only text there is the copyright message at the bottom.

</div>
<div id="footerPlaceholder"></div>
</div>
<div id="footer">
<div id="footerLinks" class="floatReverse">
<div><span id="copyright">© 2016 Microsoft</span></div>
</div>

There's an element there called "footerPlaceholder". It has no text.

So:

if ( document.getElementById("footerPlaceholder") ) {
   document.getElementById("footerPlaceholder").innerHTML = "<p>
If you need help, please look at <A href='https://company/help'>
https://company/help</A>.</p>"
}

gives us:


This then flows into the login page:


If you don't want that, you need to filter by:

//Check if we are in the HRD page
if ( document.getElementById("hrdArea") ) {

Enjoy!

Monday, June 12, 2017

ADFS : Beware copying over claims rules when they contain groups

I copy claims rules over all the time as per my earlier post.

So I did this and it wouldn't work.

Much head scratching and then I realised that  one of the claims rules was a "Send Group Membership as a Claim".

If you look at this type of rule, it looks like:

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-21-965288371-...-1106", Issuer == "AD AUTHORITY"]
 => issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Value = "Admin", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, ValueType = c.ValueType);


Notice that it has a SID and (of course) this SID is relevant to that instance of AD only.

The same group name in a different AD will have a different SID value.

So, beware, you can't just copy these types of rules over!

Enjoy!

Tuesday, June 06, 2017

ADFS : Adding extra text to the HRD screen IDP description

This is for Home Realm Discovery for Server 2012 R2 or 2016 (ADFS 3.0 or 4.0).

Building on from @Pierre's ADFS 2016 – Change the Active Directory claim provider display name in the Home Realm Discovery page.

As per that article, the IDP name is coded in the JavaScript as:

var strADCPName = "Corp Users" ;

What you can do is add a line of text beneath it:

var strADCPName = "Corp Users <br/><small>(Add some text here to suit)</small>" ;

This displays something like:


Because of the HTML there are only a limited amount of characters you can add but it is a neat way to add a better description.

If you are wondering how to change the icon (I just used a random one), refer:

ADFS : You can change anything in the Theme structure

Enjoy!

Friday, June 02, 2017

ADFS : OAuth token timeout

This is for Server 2016 - ADFS 4.0.


These are the OpenID Connect / OAuth options that you have.
  • Native application
  • Server application
  • Web API
and combinations of the above.

But nowhere in the wizard can you set the token timeout.

AD FS Scenarios for Developers shows the following PowerShell commands:

Add native client                         Add-AdfsNativeClientApplication
Add server application as client  Add-AdfsServerApplication
Add Web API / resource             Add-AdfsWebApiApplication

Building on this we can do:

get-AdfsNativeClientApplication

Name                       : AppA  - Native application
Identifier                 : b1...28
ApplicationGroupIdentifier : AppA
Description                :
Enabled                    : True
RedirectUri                : {ms-app://s-45...04/} 


get-AdfsServerApplication

ADUserPrincipalName                  :
ClientSecret                         : ********
JWTSigningCertificateRevocationCheck : None
JWTSigningKeys                       : {}
JWKSUri                              :
Name                                 : AppB - Server application
Identifier                           : 8e...44
ApplicationGroupIdentifier           : AppB
Description                          :
Enabled                              : True
RedirectUri                          : {https://localhost:1234/}


get-AdfsWebApiApplication

Name                                 : AppA  - Web API
Identifier                           : {https://localhost:44666/TodoListService}
AccessControlPolicyName              : Permit everyone
AccessControlPolicyParameters        :
AdditionalAuthenticationRules        :
AllowedAuthenticationClassReferences : {}
AllowedClientTypes                   : Public, Confidential
ApplicationGroupIdentifier           : AppA
ApplicationGroupId                   : 0e...cd
AlwaysRequireAuthentication          : False
ClaimsProviderName                   : {}
DelegationAuthorizationRules         :
Enabled                              : True
ImpersonationAuthorizationRules      :
IssuanceAuthorizationRules           :
IssueOAuthRefreshTokensTo            : AllDevices
IssuanceTransformRules               : @RuleTemplate = "LdapClaims"
                                                        ... Some claims ...

NotBeforeSkew                        : 0
Description                          :
PublishedThroughProxy                : False
RefreshTokenProtectionEnabled        : False
RequestMFAFromClaimsProviders        : False
ResultantPolicy                      : RequireFreshAuthentication:False
                                       IssuanceAuthorizationRules:
                                       {
                                         Permit everyone
                                       }
TokenLifetime                        : 480


And here we see a token lifetime!

But note the option only applies to web API.

To set this use:

set-AdfsWebApiApplication -TargetIdentifier "https://localhost:44666/TodoListService" -TokenLi
fetime 480

Enjoy!

Friday, May 26, 2017

Misc : Top search result in Google

Now this I like :-)


Top search result - awesome.

Enjoy!

Thursday, May 25, 2017

ADFS : The case of the slanting quote

I don't know the official name of this character but it's the double quote sign that slants to the right.

There was a question on the forum about a claims rule that threw "POLICY0002: Could not parse policy data" when the user tried to save it.

It looked like:

c:[Type == "http://adatum.com/data1holder", Issuer == "AD AUTHORITY"]
 => issue(type = "http://adatum.com/data1", Value = RegExReplace(c.Value,”'”,””);

The problem was the slanting quotes in the RegExReplace.

Using the double quote key (") on the keyboard and replacing these gives:

c:[Type == "http://adatum.com/data1holder", Issuer == "AD AUTHORITY"]
 => issue(type = "http://adatum.com/data1", Value = RegExReplace(c.Value,"'","");

which parses no problem.

Enjoy!

Monday, May 15, 2017

ADFS : You can change anything in the Theme structure

The official document around onload.js and customising the ADFS login page for 2012 R2 and 2016 (ADFS 3.0 and 4.0) is here.

The Theme directory structure looks like:

Directory of C:\...\Theme\Custom
05/11/2017 09:26 PM .
05/11/2017 09:26 PM ..
05/11/2017 09:26 PM css
05/11/2017 09:26 PM illustration
05/11/2017 09:26 PM images
05/11/2017 09:26 PM script
0 File(s) 0 bytes


Directory of C:\...\Theme\Custom\css
05/11/2017 09:26 PM .
05/11/2017 09:26 PM ..
05/11/2017 09:26 PM 8,144 style.css
05/11/2017 09:26 PM 8,146 style.rtl.css
2 File(s) 16,290 bytes

Directory of C:\...\Theme\Custom\illustration
05/11/2017 09:26 PM .
05/11/2017 09:26 PM ..
05/11/2017 09:26 PM 116,699 illustration.png
1 File(s) 116,699 bytes

Directory of C:\...\Theme\Custom\images
05/11/2017 09:26 PM .
05/11/2017 09:26 PM ..
05/14/2017 08:45 PM idp
0 File(s) 0 bytes

Directory of C:\...\Theme\Custom\images\idp
05/14/2017 08:45 PM .
05/14/2017 08:45 PM ..
05/11/2017 09:26 PM 931 idp.png
05/11/2017 09:32 PM 1,727 localsts.png
05/11/2017 09:26 PM 1,977 otherorganizations.png
3 File(s) 4,635 bytes

Directory of C:\...\Theme\Custom\script
05/11/2017 09:26 PM .
05/11/2017 09:26 PM ..
05/11/2017 10:23 PM 5,925 onload.js
1 File(s) 5,925 bytes 


The document refers to onload.js but you can use the same commands for any of the above files.

@Pierre e.g. has blogged on changing the "thumbnails" here.

The command for this then looks like:

Set-AdfsWebTheme -TargetName MyTheme -AdditionalFileResource @{Uri="/adfs/portal/images/idp/idp.png";path="C:\MyTheme\images\idp\idp-adfs.png"} 

Enjoy!

Friday, May 12, 2017

ADFS : Avoiding the Home Realm Discovery screen by using a link

I was asked if given the HRD screen is just "buttons" and there was a "link" behind that and users always picked the same one. could they not simply add the "link" to their web site and avoid the HRD screen altogether?

The basic problem with this is that the link is not static – it’s actually dynamic.

Take SAML-P as a example.

Assume the HRD screen has two entries; ADFS A and ADFS B.

The SAML request from the application to ADFS A gives:

https://adfs-a/adfs/ls/?SAMLRequest=

And then selecting ADFS B via HRD gives:

https://adfs-b/adfs/ls/?SAMLRequest=...

The SAML request itself is different because as the packet goes through each ADFS, it’s signed with that ADFS’s signing key.

So the "link" would have to be updated every time a signing certificate changed.

In addition, a section of the SAML AuthnRequest contains:

IssueInstant="2017-05-11T01:27:25.56Z"

The request contains the time the request was sent – which is dynamic.

There is an ADFS parameter for SAML called “clock skew”.

ADFS rejects the request when the skew between the server clock and the time in the request is more than 30 seconds (by default).

So nice try but no cigar!

Enjoy!

ADFS : Augmenting the default JWT with additional attributes

This is for Server 2016 - ADFS 4.0.

The standard use case is for an ASP.NET application using OpenID Connect / OAuth via the NuGet OWIN packages taking to ADFS.


The standard way is to configure a server application as above.

This all works . The problem is that the ADFS wizard does not have any way to configure claims rules. As a result you get the standard set of claims in the JWT.

aud     8173...1501
iss     https://xxx.cloudapp.net/adfs
iat     1494378378
exp     1494381978
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant     1494378377
nonce     6362....NmVl
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier     0JrA...H7k=
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn     user1@dev.local
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name     DEV\user1
c_hash     baOc...R8GA


The only user specific details are the "upn" and the "name".

But what if you want more?

You can use the "Web browser accessing a web application" profile as per this.

This profile achieves the result in a roundabout way:

"Behind the scenes, this template creates a native client and new app type called Web application, which is just a Web API with an Identifier (RPID) that matches the native client's client ID. This means the Web application is simultaneously client and resource, so you can assign issuance transform rules as you would with a Web API."

I configured some claims in the wizard:


Now when I authenticate, I get this:

aud     c906...3ab4
iss     https://xxx.cloudapp.net/adfs
iat     1494379409
exp     1494383009
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant     1494379408
nonce     6362...NTk2
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier     PRUA...50U=
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name     DEV\user1
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn     user1@dev.local
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname     User1
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname     Test
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress     user1@company.com
http://schemas.xmlsoap.org/claims/CommonName     User1 Test
apptype     Public
appid     c906...3ab4
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod     urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
ver     1.0
http://schemas.microsoft.com/identity/claims/scope     openid
c_hash     -0ZX...Z81A


You will also notice that the apptype is "Public" i.e. a web site as opposed to "confidential" i.e. a web API.

I also answered a question in stackoverflow along these lines here.

As per the discussion from @Efrain:

"As indicated in @nzpcmad's answer, it appears that custom claims in the id_token using the default URL-parameter-encoded GET redirect is simply not supported. The reason for this may be that there is an URL length limit, but I find that quite questionable.

Anyway, apparently this restriction does not apply when the token is returned in a POST redirect. That's also why people describe it working just fine for MVC applications.

So I was able to work around the problem by redirecting the response to a back-end API endpoint (POST), which just redirects it to the front-end (SPA) again, but as a GET request with URL-encoded parameters:

public class LoginController : ApiController
{
    [HttpPost]
    [Route("login")]
    public HttpResponseMessage Login(FormDataCollection formData)
    {
        var token = formData["id_token"];
        var state = formData["state"];
        var response = Request.CreateResponse(HttpStatusCode.Moved);
        var frontendUri = ConfigurationManager.AppSettings["ad:FrontendUri"];
        response.Headers.Location = new Uri($"{frontendUri}#id_token={token}
                                    &state={state}");
        return response;
    }
}
Note that to change the response method from GET to POST, one simply has to add &response_mode=form_post to the OAuth request URL."

Enjoy!

Monday, May 08, 2017

ADFS : Passing NameID across CP and RP

Imagine the following:

CP A --> federated with CP B --> RP

So a user goes to the RP and via HRD on CP B selects CP A and authenticates against AD.

The claims derived from CP A need to be passed across.

On CP A we have the standard LDAP rules since the user authenticated against that AD.

One of the claims we want to pass across is NameID.

You have to configure pass-through rules on CP B and the RP.

So the claims are configured in three places.

The problem is that NameID never makes it across.

There are a number of posts from people reporting the same thing but no solution.

The way I got around it was:

Assume that we want sAMAccountName to be NameID.

On CP A, have a LDAP rule:

sAMAccountName --> http://company/claims/sAMAccountName

Plus pass-through all the other claims.

On CP B, have a Transform rule:

http://company/claims/sAMAccountName --> NameID

Plus pass-through all the other claims. 

In the RP, pass-through all the claims including NameID.

Now imagine you have two RP, RP A and RP B.

RP A wants sAMAccountName to be NameID.

RP B wants UPN to be NameID.

Now we have a problem because they both share the same pipeline CP A and CP B. You can't have two different rules both passing NameID.

What you have to do on CP A is:

UPN --> http://company/claims/UPN

Here you pass both http://company/claims/sAMAccountName and http://company/claims/UPN through on CP B and then transform them at the RP level to the NameID; one for each.

Enjoy!

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) ..."

WTF?

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

<AutoParameterizationWebConfigConnectionStrings>False</AutoParameterizationWebConfigConnectionStrings>

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

Enjoy!

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.

Enjoy!

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,

Enjoy!

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.

Enjoy!

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.

Update: But beware copying groups over.

Enjoy!

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 == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
 => 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 == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
 => 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:

try
{
    // Dummy values to illustrate the principle.

    List<string> claimValues = new List<string>();
    claimValues.Add("123456");
    claimValues.Add("654321");
    claimValues.Add("456123");
                
    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);
}
 

Enjoy!

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.

Enjoy!

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: ..."

Enjoy!

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:

V1.0:

https://login.microsoftonline.com/common/.well-known/openid-configuration

V2.0:

https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

Notice the extra "V2.0".

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

https://login.microsoftonline.com/common/discovery/keys

And for V2.0 :

https://login.microsoftonline.com/common/discovery/v2.0/keys

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!

Enjoy!

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:

https://my-pc/WebService

And the API's inside this are like:

https://my-pc/WebService/api/my-api/

The web API in the wizard has the RP identifier:

https://my-pc/WebService

(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"
clientCredential);

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,  
"https://my-pc/WebService/api/my-api/");
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.

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

Enjoy!

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 :-)

Enjoy!

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:

Firstname,Lastname,Maildomain,SAM,OU,Password,Email
Joe,Bloggs,mydomain.local,jbloggs,"OU=MyUsers,DC=mydomain,DC=local",password,jb@abc.co.nz

Script

$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
}    

Enjoy!

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 company.co.nz 

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

Skew

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 

Enjoy!

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
        [HttpPost]
        public int Add([FromBody] Maths info)
        {
            int number1 = info.number1;
            int number2 = info.number2;

            return (number1 + number2);
        }

        // POST: api/Maths
        [HttpPost]
        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
            config.MapHttpAttributeRoutes();

            /*config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );*/

            config.Routes.MapHttpRoute(
                name: "ControllerAndAction",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new {id = RouteParameter.Optional}
            );
        }
    }
}

In SwaggerConfig.cs, uncomment:

c.DisableValidator();

to avoid the error message.

Now if I navigate to:

http://localhost:44571/swagger

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:

api/Maths/Add

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

Running the script, we see:


Enjoy!

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=3.5.0.0, 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>

where:
  • 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. 

Enjoy!

Monday, January 09, 2017

AD : ERROR_DS_CONSTRAINT_VIOLATION (0x8007202f)

I was helping a customer who kept getting:

ERROR_DS_CONSTRAINT_VIOLATION (0x8007202f)

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 :-)

Bonus

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.

Enjoy!