March 19, 2013 at 3:39 PM

The purpose of this blog post is to give you a complete overview on how to configure Windows Azure Active Directory (WAAD) together with Windows Azure Access Control Service (ACS). We will create 2 applications (an MVC 4 Web application and a Windows Store Application).

In our MVC application we will support Forms Based Authentication and Claims Based Authentication at the same time.

In our Windows Store Application we will use the JWT token generated by ACS to authenticate on our previously created MVC Web Api controllers.

On both applications we will be able to authorize users based on their roles (CIA users and U.S. Marine users).

 

This blog post will consist of 2 parts:

1. Setting up ACS, WAAD and an MVC Application by using Jason Web Tokens (this part)

2. Add support for Windows Store Applications by passing the Jason Web Token

 

1. Configuration on Windows Azure

Prerequisites

  • .Net Framework 4.5 (Visual Studio 2012)
  • Azure subscription
 

1.1 Configuring WAAD

1. Create / or use an existing WAAD account (our sample WAAD domain is "natsec"). Sign-up: (https://activedirectory.windowsazure.com/Signup/QuickSignup.aspx?ru=https://activedirectory.windowsazure.com&ali=1)

windows_azure_active_directory_create

2. Login on https://activedirectory.windowsazure.com/

3. From here it is pretty straightforward. You can sync an existing AD with your WAAD or create users / groups manually.

For our sample, I've created the following

            2 groups
                - Central Intelligence Agency 
                - US Marine


            3 users:
                - David Estes (member of Central Intelligence Agency group)
                - Saul Goodman (member of Central Intelligence Agency group)
                - Mike Faber (member of US Marine group)


        windows_azure_active_directory_groups windows_azure_active_directory_users     

 

1.2. Configuring ACS

1. Login to your current Windows Azure Subscription (https://manage.windowsazure.com) 
2. Navigate to Active Directory. Create a new Access Control Namespace and after creation, click on the management link (https://[your_namespace].accesscontrol.windows.net/v2/mgmt/web).

 

1.2.1. Create a new Identity Provider

1. Select WS-Federation Identity provider, click next.

2. Enter a display name, select the Url radio button for WS-Federation metadata and enter

https://accounts.accesscontrol.windows.net/[your_namespace].onmicrosoft.com/FederationMetadata/2007-06/FederationMetadata.xml 

In the above url replace [your_namespace] and browse to it to make sure it works.


azure_access_control_service_identity_provider

 

1.2.2. Create a new Relying Party Application

1. As Realm you type your application realm (free to choose, but must be unique in your ACS). ex. urn:NatSec

2. As Return Url, you enter the Url that your MVC front-end will run on. You can change this later to match with your local IIS Express port. The subpath of this URL will be discussed later in this post (needed to set our cookies and/or JWT token).

3. As Token Format we will choose JWT (Jason Web Token: at time of writing it is still in beta, but currently the most compact token available).

4. Under Authentication Settings we select our Identity Provider we just created and check "Create new rule group".

5. For Token Signing Settings we opt for a new generated Symmetric Key. We work with a symmetric key as a certificate would require the MVC website to have it installed in the Certificate Store. As we will host the website on Windows Azure Websites, we currently can't add the certificate.

azure_access_control_service_add_relying_party

 

 

1.2.3. Changing Default Rule Group

In ACS you can map WAAD Claims to your custom claims. To distinguish user access in our application we could map WAAD User Roles to custom User Roles we can use in our application (Another relying party could map role claims to our same custom format). The only problem we face is that the roles are not included in the claims (claims could become very large). When we need to request the current user roles, we need to query the GraphApi from WAAD. The mapping of user roles will happen on our MVC backend later.

1. Edit the Rule Group and Generate default rules. (this way we will receive standard claims like 'firstname', 'lastname', ... )

In order to please ASP.Net Security we will need to modify an existing claim. ASP.Net AntiForgery expects the following claims to be present:

2. Edit the name claim and change the output claim to "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" (in the predefined selectable types)

change_input_claim

 

1.3. Creating a Trust relation ship between ACS and WAAD

 

Prerequisites

 

WAAD should be configured to trust our ACS to issue a token. The only way we currently can configure this is with PowerShell.

Enter the following statements

connect-msolservice 
import-module msonlineextended –force 
$replyUrl = New-MsolServicePrincipalAddresses -Address "https://[yournamespace].accesscontrol.windows.net/" 
New-MsolServicePrincipal -ServicePrincipalNames @("https://[yournamespace].accesscontrol.windows.net/") -DisplayName "[justAName]" -Addresses $replyUrl

You will be prompted for your WAAD credentials after line 1 (ex. david.estes@natsec.onmicrosoft.com).

On line 3 you'll need to replace [yournamespace] with the namespace you choose for ACS (ex. coditblog)

On line 4 you'll need to replace [yournamespace] with the namespace you choose for ACS (ex. coditblog) and [justAName] with a descriptive name).

waad_acs_trust

Be sure to note the symmetric key credential that is created for you as output of the cmdlet if you do not supply a value. Once the service principal has been created, you cannot retrieve the symmetric key credential. You will need to use it in your code when authenticating to Windows Azure AD Access Control.

 

2. Building our MVC Application

 

2.1. Creating the MVC Application

The MVC Application will act as an API to issue a token for our Windows Store Application. It will contain all configuration in order to authenticate with ACS. 
Create a new MVC Application using an Internet Application as Project Template. 

vs2012_mvc_waad_create_app vs2012_mvc_waad_create_app_project_template 

Most existing applications will already have some security mechanism, so we'll jump into a real-world scenario and use Forms Authentication as our current authentication. We will extend our authentication mechanism by configuring ACS in our application. 
Using the Internet Application Template, Forms Authentication is already configured. Forms Authentication in MVC 4 is no longer using the ASP.Net MembershipProvider but instead it's using SimpleMembership.


        First of all, start the application and Create a new users (default password in the sample is: "mypassword")
        mvc_forms.create_user


Stop the application and click "Show All Files" in Solution Explorer. You will notice the mdf ASP.net Membership database file under "App_Data". Include the file into the project and double click on it.


        For the sake of simplicity, we'll create our roles using T-SQL. Right click the database (in Server Explorer) and select "New Query".
        Execute the following statement:

insert into [aspnet-Codit.Blog.Waad.Mvc-20130301113648].[dbo].[webpages_Roles] (RoleName) values ('CIA') 
insert into [aspnet-Codit.Blog.Waad.Mvc-20130301113648].[dbo].[webpages_Roles] (RoleName) values ('U.S. Marine') 
insert into [aspnet-Codit.Blog.Waad.Mvc-20130301113648].[dbo].[webpages_UsersInRoles] (UserId, RoleId) values (1, 2)

        mvc_simplemembership_configure_roles


        As you may have noticed already, the roles are different from the roles we previously configured in WAAD. This is on purpose.

 

2.2. Configuring ACS in our MVC Application

Add the following NuGet Package to your solution: "JSON Web Token Handler For the Microsoft.Net Framework 4.5" (Id: Microsoft.IdentityModel.Token.JWT (https://nuget.org/packages/Microsoft.IdentityModel.Tokens.JWT/0.1.0))

Also add a reference to System.IdentityModel (v4.0.0.0) and System.IdentityModel.Services (v4.0.0.0)


        Create a new class "CustomJwtSecurityTokenHandler"
 

using System.Collections.Generic; 
using System.IdentityModel.Tokens; 
using System.Linq; 
using System.Security.Claims; 
using Microsoft.IdentityModel.Tokens.JWT;

namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth.Handlers 
{ 
	public class CustomJwtSecurityTokenHandler : JWTSecurityTokenHandler 
	{ 
		public override ClaimsPrincipal ValidateToken(JWTSecurityToken jwt, TokenValidationParameters validationParameters) 
		{ 
			if ((validationParameters.ValidIssuer == null) && 
				(validationParameters.ValidIssuers == null || !validationParameters.ValidIssuers.Any())) 
			{ 
				validationParameters.ValidIssuers = new List<string> { ((ConfigurationBasedIssuerNameRegistry)Configuration.IssuerNameRegistry).ConfiguredTrustedIssuers.First().Value }; 
			}

			if (validationParameters.SigningToken == null) 
			{

				var resolver = (NamedKeyIssuerTokenResolver)this.Configuration.IssuerTokenResolver; 
				if (resolver.SecurityKeys != null) 
				{ 
					var keyName = Configuration.AudienceRestriction.AllowedAudienceUris[0].ToString(); 
					List<SecurityKey> skeys; 
					if (resolver.SecurityKeys.TryGetValue(keyName, out skeys)) 
					{ 
						var tok = new NamedKeySecurityToken(keyName, skeys); 
						validationParameters.SigningToken = tok; 
					} 
				} 
			} 
			return base.ValidateToken(jwt, validationParameters); 
		} 
		protected override string NameIdentifierClaimType(JWTSecurityToken jwt) 
		{ 
			return ClaimTypes.GivenName; 
		} 
	} 
}

JWT Token Validation isn't supported out of the box so we override the ValidateToken method. The main issue is that the parameters defined in the config aren't recognized so we need to read them from the config ourselves.

We do an override of the NameIdentifierClaimType method in order to pass our name claim to be set in the RolePrincipal object. By default it will use the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim. This claim contains our user's complete e-mail adress. Instead we want to greet the user with his first name. We return the default nameclaim ClaimTypes.GivenName (="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"). The result of this override is demonstrated in the following screenshot


        waad_claims_roleprincipal

 


        Next add the following configuration to your Web.Config
     

<system.identityModel> 
<identityConfiguration saveBootstrapContext="true"> 
  <audienceUris> 
	<add value="[yourRealm]" /> 
  </audienceUris> 
  <securityTokenHandlers> 
	<add type="[yourClassAndAssemblyCustomJWTTokenHandler]" /> 
	<securityTokenHandlerConfiguration> 
	  <certificateValidation certificateValidationMode="PeerTrust" /> 
	  <issuerTokenResolver type="[yourClassAndAssemblyCustomJWTTokenHandler]"> 
		<securityKey symmetricKey="[jwtSymmetricKey]" name="[yourRealm]" /> 
	  </issuerTokenResolver> 
	</securityTokenHandlerConfiguration> 
  </securityTokenHandlers> 
  <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> 
	<trustedIssuers> 
	  <add thumbprint="[thumbPrint]" name="https://[yournamespace].accesscontrol.windows.net/" /> 
	</trustedIssuers> 
  </issuerNameRegistry> 
</identityConfiguration> 
</system.identityModel> 
<system.identityModel.services> 
<federationConfiguration> 
  <cookieHandler requireSsl="false" /> 
  <wsFederation passiveRedirectEnabled="true" issuer="https://[yournamespace].accesscontrol.windows.net/v2/wsfederation" realm="[yourRealm]" requireHttps="false" /> 
</federationConfiguration> 
</system.identityModel.services>

On line 2:

We've added saveBootstrapContext="true" in order to retrieve our JWT token easily in our controller.

On line 4:

Replace [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec)

On line 7:

Replace [yourClassAndAssemblyCustomJWTTokenHandler] with the your "custom jwt class, assemblyname".

On line 10:

Replace [yourClassAndAssemblyCustomJWTTokenHandler] with the your "custom jwt class, assemblyname".

On line 11:

Replace [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec)

On line 11:

Replace [jwtSymmetricKey] with the symmetric key of your JWT (ACS Management page => Certificates and keys => Token Signing => [your RP] (WAAD - NatSec) => Copy the full 256-bit symmetric key

On line 17: 

Replace [thumbPrint] with your Service Certificate Thumbprint (ACS Management page => Service Settings => Certificates and keys => Token Signing => Service Namespace (X.509 Certificate) => Copy your thumbprint).

Replace [yournamespace] with your ACS namespace (ex. coditblog)

On line 25:

Replace [yournamespace] with your ACS namespace (ex. coditblog)

Replace [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec)


        Add requestValidationMode="4.5" to the httpRuntime element.
      

<httpRuntime targetFramework="4.5" requestValidationMode="4.5" />


        Add <allow users="*" /> as a child of the <authorization> element and above the <deny users="?" /> element.

<authorization>
  <allow users="*"/>
  <deny users="?" />
</authorization>


        Make sure any wif packages do not disable forms authentication module (comment the remove element if you have it)

<system.webServer>
<modules>
      <!--<remove name="FormsAuthentication" />-->
</modules>
</system.webServer>

 

When you run the application you will be automatically promted with the following screen:  
waad_sign_in

 

 

2.3. Support ACS and Forms Authentication

In order to allow Forms Based login, we need to disable the auto redirect to the Windows Azure Active Directory login page. We can do this by setting "passiveRedirectEnabled" to false in our web.config (wsFederation element). 
When we now run the application we can use the standard Forms Authentication but the WAAD seems to be disabled. We need to manually add a link in order for the user to login using WAAD.
We will and cannot use the build in Authentication Providers class for this demo. Instead locate the following line in Login.cshtml
      

@Html.Action("ExternalLoginsList", new { ReturnUrl = ViewBag.ReturnUrl })


and replace it with

<a href="https://[yournamespace].accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=[yourRealm]" title="Log in using your Windows Azure Active Directory account">Windows Azure Active Directory</button>

Make sure you replace the following in the above URL

    • [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec) 
    • Replace [yournamespace] with your ACS namespace (ex. coditblog)  
 

waad_login_option


        In order to authenticate successfully with our ACS we need to make sure we support the configured Return Url (in ACS).

We will create a new WebApi controller to act as our return Url. You could also use a normal controller but our WebApi controller will become in handy when we also support Windows Store Applications. We use WebAPI for returning tokens and JSON data, we will use normal Controllers for returning Views. Create a new folder named "api" under "Controllers". Add a new Empty Web API Controller named "AuthController".

 

        empty_web_api_controller


Add the following
      

using System.Net; 
using System.Net.Http; 
using System.Web.Http;

namespace Codit.Blog.Waad.Mvc.Controllers.api 
{ 
	public class AuthController : ApiController 
	{ 
		[AllowAnonymous] 
		[HttpPost] 
		public HttpResponseMessage GetToken() 
		{ 
			var response = Request.CreateResponse(HttpStatusCode.Redirect); 
			response.Headers.Add("Location", "/"); 
			return response; 
		} 
	} 
}


When the return url is configured correctly in ACS, then this controller action will be called after the login procedure of WAAD.
It will receive a HttpPost with security tokens, these tokens will result in FedAuth cookies.


Fiddler Return Url request from WAAD:

fiddler_token_response


Fiddler Cookies on Redirect to home page:

fiddler_fedauth_cookies

 

And we're in: 

waad_login_success

 

 

 

2.4. Enabling WAAD Graph Api and Role Authentorization

We've already discussed authentication before, now it is time for authorization. Based on the roles of a user we will allow or deny page requests.

 

2.4.1. References and Packages

Add the following NuGet Package Windows Azure Authentication Library (Beta) to your solution

Id: Microsoft.WindowsAzure.ActiveDirectory.Authentication (https://nuget.org/packages/Microsoft.WindowsAzure.ActiveDirectory.Authentication/0.6.1)

Add a service reference to your WAAD using the following URL: https://graph.windows.net/[yourWaadDomain]

Replace [yourWaadDomain] by your Waad Domain (obviously...) ex. NatSec

Reference following assemblies:

System.Windows.Forms (v4.0.0.0)

 

In order to access the GraphApi, we need to give read-permissions. Fire up a PowerShell instance and enter following commands.

First of all we need to retrieve the RoleMemberObjectId (so it can be replaced in the next command), type the following command:

Get-MsolServicePrincipal | select DisplayName,ObjectId

 

After executing the command below we will have read-only access to the WAAD Directory. 

Add-MsolRoleMember -RoleName "Service Support Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId [yourRoleMemberObjectId]

 

2.4.2. Interaction with the GraphApi

Add a new class named "GraphClient" (see inline for detailed explanations)

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using Codit.Blog.Waad.Mvc.NatSec.Microsoft.WindowsAzure.ActiveDirectory;

using Microsoft.WindowsAzure.ActiveDirectory.Authentication;



namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth.GraphApi

{

    /// <summary>

    /// This class uses the Windows Azure Graph API to retrieve a given user's direct group memberships.

    /// Except for the <see cref="GetUserGroups"/> method, the code in this class is heavily based on public

    /// Graph API examples from Microsoft.

    /// </summary>

    public class GraphClient

    {

        private readonly DirectoryDataService _dataService;

        private readonly string _tenantDomainName;

        private readonly string _servicePrincipalSymmetricKey;

        private readonly string _tenantId;

        private readonly string _appPrincipalId;



        // ReSharper disable InconsistentNaming

        /// <summary>

        /// Name of the HTTP header that contains the unique ID of a client request.

        /// Used when making Graph API requests

        /// </summary>

        private const string HeaderName_ClientRequestId = "client-request-id";



        /// <summary>

        /// Authorization header used when making Graph API requests

        /// </summary>

        private const string HeaderName_Authorization = "Authorization";



        /// <summary>

        /// Version of the data contract, used when making Graph API requests

        /// </summary>

        private const string HeaderName_DataContractVersion = "x-ms-dirapi-data-contract-version";

        // ReSharper restore InconsistentNaming



        private const string DataContractVersion = "0.8";



        public GraphClient(string tenantDomainName, string tenantId, string appPrincipalId, string servicePrincipalSymmetricKey)

        {

            _tenantDomainName = tenantDomainName;

            _tenantId = tenantId;

            _servicePrincipalSymmetricKey = servicePrincipalSymmetricKey;

            _appPrincipalId = appPrincipalId;



            _dataService = new DirectoryDataService(GetConnectionUri());

            _dataService.SendingRequest += (sender, args) =>

            {

                var webRequest = ((HttpWebRequest)args.Request);

                webRequest.Headers.Add(HeaderName_Authorization, GetAuthorizationHeader());

                webRequest.Headers.Add(HeaderName_DataContractVersion, DataContractVersion);

                webRequest.Headers.Add(HeaderName_ClientRequestId, Guid.NewGuid().ToString());

            };

        }



        /// <summary>

        /// Identifier of the issuing resource for the symmetric key.

        /// Used for getting an access token for requests.

        /// Looks something like this:

        /// A4423C7C-35F7-4679-99F8-5921A0482B63@tenantdomain.onmicrosoft.com

        /// (the GUID here is an example, not the actual value)

        /// </summary>

        private string GetSymmetricKeyIssuingResource()

        {

            return _appPrincipalId + "@" + _tenantDomainName;

        }



        /// <summary>

        /// Realm identifier for the service we want to access. Which is the tenant domain

        /// hosted at Windows Azure AD.

        /// 00000002-0000-0000-c000-000000000000/graph.windows.net@yourtenantdomain.onmicrosoft.com

        /// (the GUID here is the actual identifier)

        /// </summary>

        private string GetServiceRealm()

        {

            return AzureAdPrincipalId + "/" + GraphApiServiceHost + "@" + _tenantDomainName;

        }



        /// <summary>

        /// URI of the Graph API we're accessing.

        /// Looks something like this:

        /// https://graph.windows.net/EA314D90-8751-4534-A96E-77A5DF0E72AB

        /// (the GUID here is an example, not the actual value)

        /// </summary>

        private Uri GetConnectionUri()

        {

            return new Uri(string.Format(@"https://{0}/{1}", GraphApiServiceHost, _tenantId));

        }



        /// <summary>

        /// URI of the WAAD tenant domain.

        /// Looks something like this:

        /// https://accounts.accesscontrol.windows.net/yourtenantdomain.onmicrosoft.com

        /// </summary>

        private string GetFullTenantUri()

        {

            return "https://accounts.accesscontrol.windows.net/" + _tenantDomainName;

        }



        /// <summary>

        /// This is the known identifier of the WAAD service principal

        /// </summary>

        private const string AzureAdPrincipalId = "00000002-0000-0000-c000-000000000000";



        /// <summary>

        /// This is the domain name of the WAAD Graph API service

        /// </summary>

        private const string GraphApiServiceHost = "graph.windows.net";



        /// <summary>

        /// Method to get the Oauth2 Authorization header from WAAD. This basically authenticates

        /// the calling application to WAAD and gets a token with which further requests can be made.

        /// </summary>

        private string GetAuthorizationHeader()

        {

            var authContext = new AuthenticationContext(GetFullTenantUri());

            var credential = new SymmetricKeyCredential(GetSymmetricKeyIssuingResource(), Convert.FromBase64String(_servicePrincipalSymmetricKey));

            AssertionCredential assertionCredential = authContext.AcquireToken(GetServiceRealm(), credential);

            return assertionCredential.CreateAuthorizationHeader();

        }



        /// <summary>

        /// Queries the Graph API for security groups in which an active user with the given

        /// <paramref name="userPrincipalName"/> is a direct member.

        /// </summary>

        public IList<Group> GetUserGroups(string userPrincipalName)

        {

            var user = _dataService.Users

                .Where(u => u.AccountEnabled == true && u.UserPrincipalName == userPrincipalName)

                .AsEnumerable()

                .SingleOrDefault();



            var groupReferences = _dataService.LoadProperty(user, "MemberOf")

                .OfType<ReferencedObject>()

                .Where(r => r.ObjectType == "Group")

                .Select(g => g.ObjectId);



            return _dataService.Groups

                .AsEnumerable()

                .Where(g => groupReferences.Contains(g.ObjectId))

                .ToList();

        }

    }

}

 

Add a new class named GraphApiClaimAuthenticationManager (will use the GraphApi class)

Please replace [yourSymmetricKey] and [appPrincipalId] with the values you received when creating your WAAD using powershell.

Please see inline for detailed explanation. 

using System.Collections.Generic;

using System.Linq;

using System.Security.Claims;

using System.Web.Http;

using Codit.Blog.Waad.Mvc.Infrastructure.Auth.GraphApi;



namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth.GraphApi

{

    /// <summary>

    /// This class uses <see cref="GraphClient"/> to read the AD security groups in which a given

    /// <see cref="ClaimsPrincipal"/> is a direct member and then converts the memberships to group

    /// claims. The group claims are then used to authorize user actions with <see cref="AuthorizeAttribute"/>.

    /// </summary>

    public class GraphApiClaimAuthenticationManager : ClaimsAuthenticationManager

    {

        /// <summary>

        /// This is an arbitrary string identifying the claims issued by this class.

        /// It exists to make it possible to distinguish those claims from other claims.

        /// </summary>

        private const string ClaimIssuerName = "waad-demo-usergroup-roleclaim";



        /// <summary>

        /// Augument <paramref name="incomingPrincipal"/> with claims obtained by converting

        /// the user's direct security group memberships to role claims.

        /// </summary>

        public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)

        {

            if (incomingPrincipal == null || !incomingPrincipal.Identity.IsAuthenticated)

                return incomingPrincipal;



            // TODO: Load user roles from cache to avoid roundtrips to GraphApi

            // TODO: Load these keys from persistence store based on the tenantDomain

            const string appPrincipalId = "[yourAppPrincipalId]";

            const string symmKey = "[yourSymmetricKey]";

            var upn = incomingPrincipal.FindFirst(ClaimTypes.Upn).Value;

            var tenantId = incomingPrincipal.FindFirst("http://schemas.microsoft.com/ws/2012/10/identity/claims/tenantid").Value;

            var tenantDomain = upn.Split('@')[1];



            var graphClient = new GraphClient(tenantDomain, tenantId, appPrincipalId, symmKey);

            var userGroups = graphClient.GetUserGroups(upn);



            var localRoles = new List<string>();

            // Our local roles are CIA and U.S. Marine

            // The roles on WAAD are Central Intelligence Agency and US Marine

            // TODO: load these from persistence store

            foreach (var userGroup in userGroups)

            {

                switch (userGroup.DisplayName)

                {

                    case "Central Intelligence Agency":

                        localRoles.Add("CIA");

                        break;

                    case "US Marine":

                        localRoles.Add("U.S. Marine");

                        break;

                }

            }



            var roleClaims = localRoles.Select(x => new Claim(ClaimTypes.Role, x, null, ClaimIssuerName));



            ((ClaimsIdentity)incomingPrincipal.Identity).AddClaims(roleClaims);



            return incomingPrincipal;

        }

    }

}

 

There are a couple of things that can do here in order to extend functionality. If we support multiple WAAD tenants, we could load the securitykeys based on the waad domain from a persistence store.

We could also store mapping roles in our persistence store in order to configure them later. As noted before, roles of WAAD aren't the roles you will use locally.

 

In order to use the GraphApiClaimAuthenticationManager we need to add an extra line in our config that is responsible for kicking in our Authentication Manager

<claimsAuthenticationManager type="[NamespaceAndClassOfAuthManager], [assemblyName]" />

 

We would like to restrict access to our Contact page for US Marines. So only our CIA personnel will be authorized to access the contact page.

Protect our controller by replacing our Controller Action in HomeController.cs. The MultipleAuthorize class decoration is discussed later.

[MultipleAuthorize(Roles = "CIA")]

public ActionResult Contact()

{

	ViewBag.Message = "CIA Only!";



	return View();

}

 

Normally we could make use of the IsInRole method exposed by ClaimsPrincipal. However, I never succeeded on using this when we have Claims Based and Forms Based Authentication enabled. It will always falls back to Forms Based Authentication by default.

Therefore, I've implemented a custom authorization attribute that will check for claims and roles manually.

using System.Linq;

using System.Security.Claims;

using System.Security.Principal;

using System.Threading;

using System.Web.Mvc;



namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth

{

    public class MultipleAuthorize : AuthorizeAttribute

    {

        protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)

        {

            IPrincipal client = Thread.CurrentPrincipal;



            // Customer ClaimsIdentity authorization

            if ((client.Identity.GetType() == typeof (ClaimsIdentity)))

            {

                if (client.Identity.IsAuthenticated)

                {

                    var roleClaims = ((ClaimsIdentity) client.Identity).Claims.Where(x => x.Type == ClaimTypes.Role).ToList();

                    

                    if (Roles.Split(',').Any(role => roleClaims.FirstOrDefault(x => x.Value == role) == null))

                    {

                        return false;

                    }

                    return true;

                }

                return false;

            }



            // Use normal forms authorization     

            return base.AuthorizeCore(httpContext);

        }

    }

}

 

When you login either using WAAD or Forms authentication, you should be able to access the Contact page when you are part of the CIA and get an Access Denied Exception when you aren't part of the CIA.

You can download the complete sample of this first part here

 

In the next part, we'll add support for Windows Store Applications. Stay tuned!

 

Posted in: Azure | WIF

Tags: , ,


March 13, 2013 at 4:10 PM

Often we find ourselves at a customer or project explaining why it is better to have a dedicated SQL server or cluster for your BizTalk platform.

One can easily come up with the most obvious reason as to why this is important:

BizTalk is designed and optimized for parallel processing and throughput and the BizTalk databases are resource intensive: it is the heart of your BizTalk environment.
If you are sharing a SQL server between BizTalk and any of your other applications, you are pulling away those resources from BizTalk. This is in regard to memory, threads, CPU cycles, etc…

However I find that customers are hard to persuade with that argument and I cannot blame them:

  • If they look at the resource usage of their SQL server that is currently hosting their BizTalk platform, it is hardly doing anything. Memory usage is high off course (it is quite normal behavior for SQL to take up all memory for caching purposes), but the load on CPU and disk is mostly quite low.
  • They have other applications (either old or new) that need a SQL server environment. Most of the times the SQL database they need hosting is nothing more then a few dozen MB, so the tendency is there to say: what harm can it bring to host it on the BizTalk SQL?
  • They paid good money for the SQL server license(s) and BizTalk license(s) they are running their BizTalk platform on. They do not want to ask their manager for extra money again (cost of hardware/storage/licenses) because of budget limits or any other reason for that matter.

So in the end, what often happens at some projects is that the SQL server or SQL cluster hosting your BizTalk platform becomes a shared server or cluster hosting the BizTalk environment as well as several other databases.

 

Why is a shared SQL a bad thing?

Like I mentioned above, any resources you’re taking away from your SQL server, you are effectively taking away from your BizTalk platform.

Additionally, there is something on SQL called the “Max DOP” parameter or the parameter for the “maximum Degree Of Parallelism”. Learn more about max DOP on MSDN.

In short, it is the number of processors used by SQL server on a SQL Server instance to execute queries in parallel to each other in order to give the results as fast as possible.
If your Max DOP parameter is set to something higher than 1, SQL will try to process queries in parts by spreading them over a number of processors. All done by the SQL engine without you having to care about it much.

The reason why I talk about this here is that BizTalk, during configuration, will actually set this max DOP parameter to one (1) on the SQL server instance where your Message Box database is located. This is due to the fact that the BizTalk Message Box database is highly optimized and works quite differently from other "normal" databases. Other databases are just data stores where SQL needs to retrieve data from and store data to. BizTalk is built quite different in many ways while it stays in essence a data store off course. The BizTalk databases are that optimized that setting Max DOP to something higher than one (1), it will actually hurt BizTalk throughput and performance.

Having said this, your “other” non-BizTalk databases will actually perform worse in 99,9% of the cases having Max DOP set to 1.

 

So there it is, another reason to have a dedicated SQL server/cluster for your BizTalk platform.

Do you know any other reasons why sharing your BizTalk SQL environment is a bad idea? Feel free to put them in the comments.


March 6, 2013 at 4:05 PM

In many scenarios, it could be required to have a send port with a dynamic behavior:

  • Send messages to a FILE location, containing the date of today
  • Send a notification mail to many different recipients
  • Dynamically change the URL of the backend web service, depending on the incoming request

 

Many developers would solve this by using dynamic send ports.  These ports allow you to configure all adapter properties, even the adapter type, at runtime.  I’m not a big fan of dynamic send ports, because it introduces some drawbacks:

  • Dynamic send ports are very difficult to troubleshoot (a hell for your operations team)
  • Dynamic send ports require your port configuration to be stored in a secure way (custom SSO application)
  • Dynamic send ports have a bigger performance hit (solved in BTS 2013)
  • Dynamic send ports don’t allow to specify an adapter handler (solved in BTS 2013)

 

That’s why I prefer using static send ports.  With a very easy trick, you can make static send ports partially dynamic.  Mostly it’s only the connectionstring, URI, path that must be changed at runtime.  This can be achieved in a custom pipeline component in the send pipeline:

 

 

This dynamic behavior works smooth for FILE and FTP adapters.  However, when using this mechanism on WCF send ports, there’s a caveat.  You will find out that the BTS.OutboundTransportLocation of the first message will be cached on the WCF send port.  So all subsequent messages will be sent to the same location.  This port configuration is cached until your send port configuration is modified or until your adapter send handler is restarted.  Luckily: there’s a way to disable this caching:

 

 

By setting the BTS.IsDynamicSend property to true, you force the WCF send adapter not to use the cache, but the runtime value instead.  More information on this setting can be found on MSDN.

Posted in: BizTalk | General | WCF

Tags: ,


February 27, 2013 at 3:37 PM

One of the most asked change requests seems to be changing a part of a schema accompanying the modification of a mapping. Since a mapping from BizTalk can be made in the BizTalk mapper or in XSLT, it is always a bit of a surprise, how difficult this can be. It is interesting how the translation of The BizTalk mapper is done under the hood and how we can improve the performance. In this article I will concentrate  on the cumulative concatenate functoid found when working with the BizTalk mapper.

 

Project sample

An external schema contains a report about orders that have been processed. Each order line contains a validation code.
In our internal schema, we would like to use one node to know if the whole order has been processed or not. We can solve this by using the looper functoid, but in this case I will use the Cumulative concatenate functoid. 

 

The BizTalk mapper 

First we cumulate all the status values using a cumulative concatenate, afterwards, a find functoid will tell us if there is a status "NOK" (not ok) is present. Then we will determinate depending on the index if the status will end up in a true or false. This is a piece of cake.
 

 

 

The change

The change regarding this mapping, which should now be extended, the external schema can now have multiple statuses that tell us if a line has not been processed. ("NOK","NIS", "NA").  I won't implement this change, but I will concentrate in how to keep this mapping clean. 

 

Using an external xslt file

I will use an external XSLT schema for applying this change. It seems to be that if we will use another few functoids, our mapping will become a big mess. I simply generated an XSLT  from the existing mapping by right clicking the map in the solution explorer and selecting "Validate map". BizTalk will output two links, one of them will be the XSLT file. Save this file to disk and and refer from the mapping property "Custom XSLT Path" to the external XSLT file.


The first thing that strikes me is the fact that this XSLT file is nearly unreadable, or at least difficult to understand.

Under the hood, BizTalk is using at least one variable for each functoid you use. Since we cannot change the values of a variable in XSLT , BizTalk uses a script block with inline c# code at the bottom of the document for cumulating each value of the node Status.

 

 This is the generated code for the IsValid node. 

 

<xsl:variable name="var:v1"
select="userCSharp:InitCumulativeConcat(0)" /> 

Initialize the Cumulative array: The 0 refers to the fact that this is the first cumulative concatenation we are using in our mapping. This is the Key for our cumulative concatenation function. On that way, we are able to reuse the c# code multiple times again.

     

<xsl:for-each select="/s0:ExtOrderReport/Lines/Line">
        <xsl:variable name="var:v2" select="userCSharp:AddToCumulativeConcat(0,string(Status/text()),'1000')" />
</xsl:for-each>

Loop for each node we would like to concatenate, and give the value to the function "AddToCumulativeConcat". Note that we also use the 0 here.

      

<xsl:variable name="var:v3" select="userCSharp:GetCumulativeConcat(0)" />

Ask to the c# code to get all our values (again with the key 0)

    

<xsl:variable name="var:v4" select="userCSharp:StringFind(string($var:v3) , 'NOK')" />

Find a "NOK" value and returns an index result.
 

<xsl:variable name="var:v5" select="userCSharp:IsValidOrder(string($var:v4))" />
<IsValid>
	<xsl:value-of select="$var:v5" />
</IsValid>

Call our internal c# code to decide if this order is valid or not. 

 

Optimalizations

This seems to be a lot of code, for such a piece of logic, isn't it?

We might could manual optimize this, for performance reasons: 

 

<xsl:variable name="var:v1" select="userCSharp:InitCumulativeConcat(0)" />    
  <xsl:for-each select="/s0:ExtOrderReport/Lines/Line/Status"> 
      <xsl:variable name="var:v2" select="userCSharp:AddToCumulativeConcat(0,string(text()),'1000')" />   
   </xsl:for-each> 
     <xsl:variable name="var:v3" select="userCSharp:GetCumulativeConcat(0)" />    
  <IsValid>       
 <xsl:value-of select="not(contains($var:v3,'NOK'))" />  
    </IsValid>

 

I limited the for-each function only to the node we need, in that way, we only need to loop this node, not the whole line node.

I replaced the c# search function by the build in XSLT search function, since built-in functions are always faster than loading custom c# code.

After that, I limited the variables were possible. 

 

XML profiling

After this manually intervention, I would like to know what the result is of my improvements.

 

Visual studio offers by default XML profiling. When comparing these results, I could say that we did an improvement of nearly 50%

 

  

Conclusions

For long term maintenance reasons it is smarter to write your own xslt, and not use the built-in BTM (BizTalk mapper).

It's always possible to convert convert a BTM to an XSLT.

The BTM might be fast for easy mappings, but even for this, BTM will always generate a less readable and less performance XSLT file.

You can manually improve the mapping speed by yourself.


February 13, 2013 at 4:00 PM

At one of our customers we faced an issue with a large BizTalk DTA database. To get the growth under control, an investigation was launched to see where we tracked message context and bodies. As everyone probably knows, you can enable tracking on send ports, receive ports, pipelines and orchestrations.

Whilst doing some background research I found this interesting tool, the BizTalk tracking exporting and importing utilities from Rudolf Henning. It's hosted on codeplex so you could even consider accessing the source code an tweaking it to your needs but to be honest I found it useful "out of the box".

BTSGetTrackingOptions.exe

The main use of the first tool "BTSGetTrackingOptions.exe" is to call it as a command line utility with the parameters server (name of database server) and database (biztalk management database name), optionally which application (otherwise all), and even if required specific receive or send port, orchestration or pipeline. The tool will dump the result in the XML file of which you specify the name in the parameters. Any tracking reported will be as an "add" key so you can simply use these to import.

BTSGetTrackingOptions.exe outputfilename.xml -srv:databaseservername -db:BizTalkManagementDatabaseName -a:OptionalApplicationName -r:optionalReceivePortName /s:optionalSendPortName -o:OptionOrchestrationName -p:optionalPipelineName

BTSSetTrackingOptions.exe

The second tool "BTSSetTrackingOptions.exe" is, obviously, for importing an XML file. You can choose to add tracking or remove it and which sort of tracking (depending on the name of the key). As the application, send / receive port, orchestration or pipeline names are defined in the XML you import, there is less need to set options on this tool.

BTSSetTrackingOptions.exe importfilename.xml -s:databaseservername -d:BizTalkManagementDatabaseName

I found this tool lightened my work a lot - both in analyzing the tracking settings and in resetting them.
I even made some command files which call specific XML files so we can easily switch between full tracking and reduced tracking.

 

Below: a sample of an XML file describing the tracking settings.

 

Anyone else has some tools for managing BizTalk tracking?


February 6, 2013 at 4:09 PM

When trying to install SQL Server 2008 R2 on a Windows Server 2008 R2 Virtual Machine on Azure, you might come across the following error at the Setup Support Rules step:

 

 

This has to do with two manifest files that are corrupt.

If you have another working installation, you can copy the following two manifest files from the working server to the failing server:

  • C:\Windows\winsxs\Manifests\amd64_microsoft.vc80.atl_1fc8b3b9a1e18e3b_8.0.50727.4053_none_8a1a02152edb659b.manifest
  • C:\Windows\winsxs\Manifests\x86_microsoft.vc80.atl_1fc8b3b9a1e18e3b_8.0.50727.4053_none_d1c738ec43578ea1.manifest

 

If you don’t have another working installation, find and open the files mentioned above and paste the following xml in them:

  • AMD64 Manifest:
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <!-- Copyright © 1981-2001 Microsoft Corporation -->
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <noInheritable/>
    <assemblyIdentity type="win32" name="Microsoft.VC80.ATL" version="8.0.50727.4053" processorArchitecture="amd64" publicKeyToken="1fc8b3b9a1e18e3b"/>
    <file name="ATL80.dll" hash="99840dcc34e78af239d80841eba316c184e407cd" hashalg="SHA1"/>
    </assembly>
  • X86 Manifest:
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <!-- Copyright © 1981-2001 Microsoft Corporation –>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <noInheritable/>
    <assemblyIdentity type="win32" name="Microsoft.VC80.ATL" version="8.0.50727.4053" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"/>
    <file name="ATL80.dll" hash="6d7ce37b5753aa3f8b6c2c8170011b000bbed2e9" hashalg="SHA1"/>
    </assembly>

 

Note: You probably need to change the permissions for those files to be able to overwrite them.
To do so, go to the file properties, Security tab, Advanced, Owner tab, Edit, Select the Administrators group and click three times OK until all windows are closed.
Reopen the properties for the file, Security tab, Edit, Select the Administrators group and select Full Control.


January 28, 2013 at 2:41 PM

The BizTalk Server 2013 Beta release comes with the SB-Messaging adapter.  This adapter allows our on-premise BizTalk environment to integrate seamlessly with the Windows Azure Service Bus queues, topics and subscriptions.  Together with my colleague Mathieu, I had a look at these new capabilities.

 

Adapter Configuration

The configuration of SB-Messaging receive and send ports is really straightforward.  BizTalk just needs these properties in order to establish a connection to the Azure cloud:

  • Service Bus URL of the queue, topic or subscription:

        sb://<namespace>.servicebus.windows.net/<queue_name>

  • Access Control Service STS URI:

        https://<namespace>-sb.accesscontrol.windows.net/

  • Issuer name and key for the Service Bus Namespace

 

Content Based Routing

Both Service Bus and BizTalk messaging layer offer a publish-subscribe engine, which allows for content based routing.  In BizTalk, content based routing is done through context properties, the Azure Service Bus uses Brokered Message properties.  A BizTalk context property is a combination of the propertyName and propertyNamespace.  In Azure Service Bus, context properties are only defined by a propertyName.  How are these metadata properties passed from the cloud to BizTalk and vice versa?

 

Sending from BizTalk to Service Bus topic

In order to pass context properties to the Service Bus topic, there’s the capability to provide the Namespace for the user defined Brokered Message Properties.  The SB-Messaging send adapter will add all BizTalk context properties from this propertyNamespace as properties to the Brokered Message.  Thereby, white space is ignored.

image

 

Receiving from Service Bus subscription to BizTalk

Also at the receive adapter, there’s the possibility to pass properties to the BizTalk message context.  You can specify the Namespace for Brokered Message Properties, so the SB-Messaging adapter will write (not promote) all Brokered Message properties to the BizTalk message context, within the specified propertyNamespace.  Be aware when checking the option Promote Brokered Message Properties, because this requires that a propertySchema is deployed which contains all Brokered Message properties.

 

image

 

Receive Port Message Handling

I was interested in the error handling when an error occurs in the receive adapter or pipeline.  Will the message be roll backed to the Azure subscription or suspended in BizTalk?  Two fault scenarios were investigated.

 

Receive adapter unable to promote property in BizTalk context

In this case, we configured the receive port to promote the context properties to a propertyNamespace that did not contain all properties of the Brokered Message.  As expected, the BizTalk adapter threw an exception:

  • The adapter "SB-Messaging" raised an error message. Details "System.Exception: Loading property information list by namespace failed or property not found in the list. Verify that the schema is deployed properly.

The adapter retried 10 times in total and moved afterwards the Brokered Message to the dead letter queue of the Service Bus subscription.  Afterwards, BizTalk started processing the next message.

 

Receive pipeline unable to parse message body

In this simulation we tried to receive an invalid XML message with the XMLReceive pipeline.  After the error occurred, we discovered that the message was suspended (persisted) in BizTalk.

 

WCF-Adapter Framework

It’s a pity to see that this new adapter is not implemented as a WCF-binding.  Due to this limitation, we can’t make use of the great extensibility capabilities of the WCF-Custom adapter.  The NetMessagingBinding could be used, but I assume some extensibility will be required in order to transform BizTalk context properties into BrokeredMessageProperty objects and vice versa.  Worthwhile investigating…

 

Conclusion

The BizTalk product team did a great job with the introduction of the SB-Messaging adapter!  It creates a lot of new opportunities for hybrid applications.

Posted in: Azure | BizTalk | Service Bus | WCF

Tags:


January 14, 2013 at 3:16 PM

Lately I came across a weird exception while building a BizTalk project with Team Foundation Server.  Let’s have a look.

 

The error

Although the build succeeded locally, we got this error while building our BizTalk project with TFS:

FlatFileSchema.xsd.cs: The type or namespace name '<ContextPropertyName>' could not be found in the global namespace (are you missing an assembly reference?)

 

The troubleshooting

After some try/error, we discovered that the error only occurred when building a BizTalk project in TFS that contains a flat file schema, using property promotion.   The first reflex was to compare the installed software on our local development machine and the Team Foundation Server.

  • Local machine: Full BizTalk 2010 Installation
  • TFS server: BizTalk 2010 Build Component

 

 

The solution

The solution was to install also the Developer Tools and SDK on the TFS server.  Although MSDN states that the Build Component should be sufficient.  This extra installation adds “Developer Tools\Schema Editor Extensions” to the BizTalk folder.  This is apparently needed for building flat files schemas that use property promotion.

Posted in: BizTalk | General | Schemas | TFS

Tags: ,


January 9, 2013 at 2:34 PM

Today I've encountered a problem with the party configuration, specifically while reconfiguring send ports from a party. An error occurred when I removed a send port from a party and saved my settings.

 

The error

The error occurred during the removing of a send port from a party: Sendport Reference [Microsoft.BizTalk.B2B.PartnerManagement.SendPortReference] cannot be deleted as it is use by agreement [Microsoft.BizTalk.B2B.PartnerManagement.SendPortReference]. (Microsoft.BizTalk.B2B.PartnerManagement)

 

The troubleshooting

Based on a forum thread on MSDN, this seems to be a bug in the BizTalk console, but has never been officially announced or patched. The forum thread suggests waiting for the next cumulative update for BizTalk server [link: cumulative updates Biztalk]. But even when I applied an update for BizTalk to the next version, the problem didn't got solved.

Based on the error message, a constraint prohibits removing the send port record assigned to this party. The OnewayAgreementSendPortReference table contains all send port records for each party. A foreign key is present between OnewayAgreementSendPortReference.SendPortReferenceId and the SendPortReference.Id colomn.

The solution

I had to manually remove the Foreign key record from the SendPortReference table.

Connect to the BizTalk SQL database instance of the BizTalkMgmtDb catalog.
Change the @Name variable to the name of the send port you would like to remove from the party.

DECLARE @NAME varchar(100)
SET @NAME = 'spo_3025816970108_3014683300200_INVRPT_EDI_FTP_PRD'

DECLARE @ID int
SELECT @ID = id
  FROM [BizTalkMgmtDb].[tpm].[SendPortReference]
  WHERE Name = @NAME  
  
DELETE FROM [BizTalkMgmtDb].[tpm].[OnewayAgreementSendPortReference]
  WHERE SendPortReferenceId = @ID

  DELETE FROM [BizTalkMgmtDb].[tpm].[SendPortReference]
  WHERE Name = @NAME

After running this query, refresh your BizTalk administration console and the send port is now removed from the party.

Important note:
Be aware that fiddling with the BizTalk internal databases is not a recommended approach and is not supported by Microsoft.
Any support case dealing with issues after manually changing the BizTalk internal databases will have to be paid fully or result in a re-installation!


January 4, 2013 at 2:47 PM

A week ago, I had a problem with a Flat File Schema in BizTalk 2010. I thought I was doing everything right, but I forgot one important thing.

Below you can find the flat file we have to create a schema for.

N John      Smith    
A Fakestreet 123      New York            USA      
A Fakestreet 123      Newark              USA      
N Mathieu   Vermote  
A Fakestreet 123      Ghent               Belgium   
A Fakestreet 123      Roeselare           Belgium   
N Jane      Doe      
A Fakestreet 123      Los Angeles         USA      
A Fakestreet 123      Santa Cruz          USA      
A Fakestreet 123      San Francisco       USA      
N Jack      Doe      
N Jake      Doe         
A Fakestreet 123      Miami               USA   
A Fakestreet 123      Fort Lauderdale     USA   

So we have two type of lines, a name-line (starts with N) and a address-line (starts with A).

In the schema there should be a sequence with N and A repeating and A can occur 0 to many times.

 

So I made a schema looking like this:

image

The minOccurs and maxOccurs set to this:

N: 0 – 1

A: 0 – unbounded

The Tag Identifier is also set to ‘N ‘ for element Name and to ‘A ‘ for the element Address.

 

When I try to ‘Validate Instance’ in Visual Studio I get the error:

Unexpected data found while looking for: ‘A ‘

 

The solution to this error was to set the Parser Optimization to Complexity.

image

 

When the Parser Optimization is set to ‘speed’, which is the default value, the schema is optimized to decrease the parsing time. This is no problem for the most flat files, but for complex flat files it can be a problem.

So setting the value to ‘complexity’ can help your flat file schema parse complex flat files.

 

More info about the Parse Optimization property on MSDN: http://msdn.microsoft.com/en-us/library/aa578137.aspx

Posted in: BizTalk | Schemas

Tags: