March 29, 2013 at 3:26 PM

Today I was deploying a new release for one of my BizTalk applications. I updated logic on several places in two assemblies containing orchestrations.

I decided to deploy to the testing server manually: the first assembly was no problem to deploy, however the second assembly gave me the following error:

 

Failed to update binding information. (mscorlib)

Party 'PartyName' enlisted under role 'Provider(Codit.MyRoleLinkType)' has not bound all the operations of role link port types. (Microsoft.BizTalk.ExplorerOM)

 

You do not find a lot of information on the above error online, so I tried to check where the problem lied myself to no avail.

I found one similar blog post from Steve Harclerode which claims that there is no other way than to delete the entire BizTalk application and then recreate it.

However, I found that if I delete this particular resource and then add it again, I have no issues.
Off course, by removing the resource and thus also removing the artifacts and bindings, you also lose your role link setup. In my case however, this is easily put back by importing the bindings I exported before removing the assembly.

Hope this helps someone, please feel free to comment if you have any cause or solution.


March 27, 2013 at 4:10 PM

BizTalk provides out-of-the-box functionality to include your custom BizTalk database in the standard backup procedure. 
You can find all details on MSDN.  Here’s a small synopsis:

 

  • Execute these scripts against your custom database
    • %BTSINTALLDIR%\Schema\Backup_Setup_All_Procs.dsl
    • %BTSINTALLDIR%\Schema\Backup_Setup_All_Tables.dsl

 

  • Add the SQL Agent account to the BTS_BACKUP_USERS role of your custom database

 

  • Modify the adm_OtherBackupDatabases table of the BizTalkMgmtDb, add a record for your custom database

 

        image

 

However, when I executed the BizTalk Backup Job after this change, I got the following exception:

 

  • [SQLSTATE 01000] (Message 4035)  BACKUP LOG is terminating abnormally. [SQLSTATE 42000] (Error 3013)  BACKUP LOG cannot be performed because there is no current database backup.

 

The cause was the fact that it is not possible to take a transactional backup of a database, if you didn’t take a full backup first.  This issue was fixed by executing the stored procedure sp_ForceFullBackup of the BizTalkMgmtDb.  Now the custom database had a initial full backup and the BizTalk Backup Job executed successfully.  This result was an extra database backup:

 

image

Posted in: BizTalk | Database | Infrastructure | SQL Server

Tags:


March 22, 2013 at 10:20 AM

Codit is very happy to let you know that the new version of Microsoft BizTalk Server: BizTalk 2013 has been Released to Manufacturing (RTM).  This version of BizTalk Server is the flagship integration server that is built to build hybrid integration scenarios in an easier and consistent way.

This is the eighth release of BizTalk Server, and it is the eighth version where Codit has been actively working with.  As a TAP customer, we have provided feedback and did extensive testing with the product.  The cloud offerings, which will be released soon, include Infrastructure as a Service (IaaS) capabilities as well as Platform as a Service (PaaS) capabilities. Please stay tuned for exciting updates on this topic in the near future as we are working very close with the product team on these releases.

We are also proud to say that we have just released a new version of the following products, supported and tested on BizTalk Server 2013:

  • Codit Integration Dashboard: handle exceptions and resubmit messages in a user friendly web based portal.
  • Codit Integration Framework: our framework is processing millions of messages every day in projects around the world and will be doing so for the BizTalk 2013 version.  The new version of our framework contains a lot of new features.  Don't hesitate to contact us for more information.

For the on-premises BizTalk Server 2013 release, the following themes are important:

  • Cloud Connectivity
  • Ability to run existing BizTalk applications in the cloud (IaaS)
  • Improved Performance
  • Simplified Development and Management Experience
  • Support for the latest platform and standards

In terms of features, this translates to

  • Integration with Cloud Services- BizTalk Server 2013 includes new out-of-the box adapters to send and receive messages from Windows Azure Service Bus, making it easy to build hybrid solutions. It also provides capabilities to host BizTalk endpoints in Azure through the Service Bus Relay providing a simple and secure way to connect external partners and application to BizTalk Server on premises.
  • RESTful services- BizTalk Server 2013 provides adapters to invoke REST endpoints as well as expose BizTalk Server artifacts as a RESTful service.
  • Enhanced SharePoint adapter- Integrating with SharePoint using BizTalk Server 2013 is now as simple as integrating with a file share.
  • SFTP adapter-Enables sending and receiving messages from an SFTP server.
  • Other enhancements: The ESB capabilities previously introduced in the ESB Toolkit are now fully integrated with BizTalk Server, Dependency tracking,Improvements in dynamic send ports, XslCompiledTransform, more support for protocol updates (X12, EDIFACT, HL7)

There is also a change in the licensing approach, where BizTalk is now moving to a per-core licensing model.  If you need more information on this, don't hesitate to let us know.

Posted in: BizTalk | Service Bus | Azure

Tags:


March 21, 2013 at 3:16 PM

This is the second part in Using ACS and WAAD with JWT Tokens for Web and Store Applications.

If you missed Part 1 make sure you catch up first!

We will first create a Windows Store Application. Then we will make use of the AuthenticationBroker in order to authenticate with our ACS namespace.
To support this type of authentication we will modify our existing MVC application Web Api Controller (created in part 1).

1. Creating the Windows Store Application


If you don't have any experience building Windows Store Applications, there are some things you need to keep in mind:
    - To run / simulate a Windows Store Application, you will need a non-admin user in order to view your application.
    - There is an extra "Simulator" in visual studio that simulates the application instead of deploying it to your Metro interface.


windows_store_vs_simulator

 

1.1. Creating the new project


create_windows_store_project


Add a new javascript file to the solution named "authentication.js". Add the javascript reference to the default.html page.

<script src="/js/authentication.js"></script>

Add a new button and text field in the default.html page by replacing “<p>Content goes here</p>” by:

<p style="margin: 20px;"> 
    <button id="bttnAuthenticate">Login using WAAD</button> 
    <button id="bttnCall">Hit MVC without a token</button> 
    <button id="bttnTokenCall">Hit MVC with Jason Web token</button> 
    <br /> 
    <textarea cols="70" rows="10" id="log"></textarea> 
</p>

Let's add an event handler for our button:

Replace args.setPromise(WinJS.UI.processAll()); in the default.js by 
args.setPromise(WinJS.UI.processAll().done(function () { 
    var bttnAuthenticate = document.getElementById("bttnAuthenticate"); 
    var bttnCall = document.getElementById("bttnCall"); 
    var bttnTokenCall = document.getElementById("bttnTokenCall"); 
    bttnAuthenticate.addEventListener("click", bttnAuthenticateClick, false); 
    bttnCall.addEventListener("click", bttnCallClick, false); 
    bttnTokenCall.addEventListener("click", bttnTokenCallClick, false); 
}));

 

1.2. Modifying our existing Auth Api Controller


In the GetToken method we currently have the following:

[AllowAnonymous] 
[HttpPost] 
public HttpResponseMessage GetToken() 
{ 
    var response = Request.CreateResponse(HttpStatusCode.Redirect); 
    response.Headers.Add("Location", "/"); 
    return response; 
}

 

In order to pass the token to a Windows Store Apps we need to adjust the GetToken method to the following;

[HttpPost] 
[AllowAnonymous] 
public HttpResponseMessage GetToken() 
{ 
    var response = Request.CreateResponse(HttpStatusCode.Redirect); 
    var bootstrapToken = ExtractBootstrapToken(); 
    const string mvcCookieName = "tokenCookie"; 
    // We check for cookies here. If there is no "login" cookie we do not issue a token redirect (for use in Windows Store Apps) 
    if (HttpContext.Current.Request.Cookies.AllKeys.Contains(mvcCookieName)) 
    { 
        response.Headers.Add("Location", string.Format("/")); 
    } 
    else 
    { 
        response.Headers.Add("Location", string.Format("/auth/end?t={0}", bootstrapToken)); 
    } 
    return response; 
}

protected virtual string ExtractBootstrapToken() 
{ 
    var bc = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext; 
    return  ((JWTSecurityToken) bc.SecurityToken).RawData; 
}

 

Let's explain what's changed:
Whenever a request comes in that triggers an action method on any of our Web Api controllers, we will need to provide a token. Whether this request is coming from our current MVC Application or our Windows Store Application.
When we login to MVC, we need to redirect the user to the homepage.
When we login to Windows Store Application, we need to redirect to a Url that is configured in our store application as return Url for the Authentication Broker. (see later)
The redirection Url will also contain the token as a querystring.

The only way (that I know of) we can do this is by setting a cookie on the client that is included in the request when we login from MVC. The store application will not provide any cookies so we will get our custom return Url.
Setting the cookie is easy by adding the following to our Authentication Controller Login Action:

ControllerContext.HttpContext.Response.Cookies.Add(new HttpCookie("tokenCookie") { Value = "" });

 

1.3. Configuring the authentication broker

 

Great, our superfantastic GUI is all setup and our controller will redirect us to a custom URI. Time to add some javascript to start our login process. To achieve that, we will make use of the Authentication Broker

Add the following code to the authentication.js file:

function bttnAuthenticateClick() { 
    var log = document.getElementById("log");

    var requestUri = "https://[yourAcsNamespace].accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=[yourRealm]"; 
    // Add the url here that you redirect to in the Auth Api Controller 
    var callBackUri = "[yourRedirectUrl]";

    if (authzInProgress) { 
        log.value = "Authorization already in Progress ..."; 
        return; 
    }

    var startUri = new Windows.Foundation.Uri(requestUri); 
    var endUri = new Windows.Foundation.Uri(callBackUri);

    log.value = "Navigating to: " + requestUri + "\r\n";

    authzInProgress = true;

    Windows.Security.Authentication.Web.WebAuthenticationBroker.authenticateAsync( 
        Windows.Security.Authentication.Web.WebAuthenticationOptions.none, startUri, endUri) 
        .done(function (result) { 
            log.value += "Response Data | Status: " + result.responseData + "|" + result.responseStatus + "\r\n";

            if (result.responseStatus === Windows.Security.Authentication.Web.WebAuthenticationStatus.errorHttp) { 
                log.value += "Error returned: " + result.responseErrorDetail + "\r\n"; 
            }

            if (result.responseData.split('?')) { 
                token = result.responseData.split('?')[1].split('t=')[1]; 
                log.value += "JWT: " + token + "\r\n"; 
            } else {

            } log.value += "JWT token is missing... something went wrong \r\n";

            authzInProgress = false; 
        }, function (err) { 
            log.value += "Error returned by WebAuth broker: " + err; 
            log.value += "Error Message: " + err.message + "\r\n"; 
            authzInProgress = false; 
        }); 
}



Replace the following values

 

1.3.1. Debugging the authentication broker

We all know that things mostly won't work the first try. When the authentication broker doesn't work, you won't get a proper error message.
That's why we first want to check if our reply Url gets hit by adding a breakpoint. If that breakpoint doesn't get hit, there is a big chance you have a misconfiguration in ACS.

The return Url configured in ACS will trigger our MVC application. The MVC application will get us the token and we'll forward the call to a new Url, the Url defined in our Windows Store Application as "callbackUri".
This Url doesn't need to exist, it will only trigger the callback of the authentication broker.

 

1.4. Running the applications

Fire up another instance of Visual Studio (run as administrator) and open our MVC solution we created in part 1. 
Add a breakpoint to the GetToken method of our Auth Api Controller. Debug the MVC Application.
Run the Simulator to start our windows store application. Click the "Login using WAAD" button.
You should be prompted with the following screen


simulator_waad_authentication

After login, we can debug our GetToken() method:

extract_bootstraptoken

When everything went well, our Windows Store App will be able to retrieve the token

reply_from_mvc

 

2. Secure our Mvc Web Api

2.1. Modifying Global.asax for authentication


Whenever a request comes to trigger an action on one of our Web Api Controllers, we need to parse the JWT Token in order to authenticate the user.
Authentication happens in the Global.asax, authorization will happen in the Controller Action by decorating the class or method with an Authorization Attribute.

To intercept a Web Api request we can make use of the Web Api Message Handlers

Add the following to the Global.asax.cs:

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            Configure(GlobalConfiguration.Configuration);
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }

        static void Configure(HttpConfiguration config)
        {
            config.MessageHandlers.Add(new JwtTokenValidationHandler());
        }

        internal class JwtTokenValidationHandler : DelegatingHandler
        {
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                // We support Forms Based authentication
                var authCookie = request.Headers.GetCookies(FormsAuthentication.FormsCookieName).FirstOrDefault();
                if (authCookie != null)
                {
                    var authC = authCookie[FormsAuthentication.FormsCookieName];
                    ParseCookie(authC.Value);

                    return base.SendAsync(request, cancellationToken);
                }

                // We support JWT Token authentication
                HttpStatusCode statusCode;
                string token;
                if (!TryRetrieveToken(request, out token))
                {
                    // if the token could not get parsed we still allow access to our controller action methods.
                    return base.SendAsync(request, cancellationToken);
                }

                try
                {
                    var tokenHandler = new JWTSecurityTokenHandler();

                    List<SecurityKey> skeys;

                    var identityConfig = new IdentityConfiguration();
                    string keyName = identityConfig.AudienceRestriction.AllowedAudienceUris[0].ToString();

                    if (((NamedKeyIssuerTokenResolver)(identityConfig).IssuerTokenResolver).SecurityKeys.TryGetValue(keyName, out skeys))
                    {
                        var tok = new NamedKeySecurityToken(keyName, skeys);
                        var validationParameters = new TokenValidationParameters
                        {
                            AllowedAudience = keyName,
                            ValidIssuer = ((ConfigurationBasedIssuerNameRegistry)identityConfig.IssuerNameRegistry).ConfiguredTrustedIssuers.ToList()[0].Value,
                            SigningToken = tok
                        };

                        var currentPrincipal = tokenHandler.ValidateToken(token, validationParameters);

                        Thread.CurrentPrincipal = currentPrincipal;
                    }

                    return base.SendAsync(request, cancellationToken);
                }
                catch (SecurityTokenValidationException)
                {
                    return base.SendAsync(request, cancellationToken);
                }

                catch (Exception ex)
                {
                    statusCode = HttpStatusCode.InternalServerError;
                    // TODO log exception
                }
                return Task<HttpResponseMessage>.Factory.StartNew(() =>
                                                                  new HttpResponseMessage(statusCode));
            }

            private static void ParseCookie(string cookieValue)
            {
                // Parse authentication Cookie here
                FormsAuthenticationTicket enabledTicket = FormsAuthentication.Decrypt(cookieValue);

                if (enabledTicket == null) return;

                var id = new FormsIdentity(enabledTicket);
                var principal = new GenericPrincipal(id, null);
                HttpContext.Current.User = principal;
            }

            private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
            {
                token = null;

                // Substract the token from the bearer authorization header
                IEnumerable<string> authzHeaders;
                if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
                {
                    return false;
                }
                var bearerToken = authzHeaders.ElementAt(0);
                token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
                return true;
            }
        }
    }

 

You don't need to change anything, it will retrieve everything all security settings from the Web.Config.
What we try to do here is set a CurrentPrincipal when we can. When we find an authentication cookie, we parse that cookie (we support forms authentication on MVC). When we find an authentication header, we retrieve the token from the header and try to validate it. If it's valid, we know the current user.
Of course it can happen that the cookie or the authentication header is invalid. However, as we are only authenticating here, we still let the call go through to the controller action. Our controller will check if the user is authenticated or not. Why do we do this? Well, we still need some anonymous actions to be accessible for unauthenticated users.

 

2.2. Adding a Web Api AuthorizationAttrubute

We already added a MultipleAuthorization Attribute for our normal Controllers. Now it's time to add an ApiAuthorizationAttribute for our Api Controllers.

Let's create a new class called "ApiAuthorization" and add the following code

using System.Security.Principal; 
using System.Threading; 
using System.Web.Http.Controllers;

namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth 
{ 
    public class ApiAuthorizeAttribute : System.Web.Http.AuthorizeAttribute 
    { 
        protected override bool IsAuthorized(HttpActionContext context) 
        { 
            IPrincipal client = Thread.CurrentPrincipal; 
            return client.Identity.IsAuthenticated; 
        } 
    } 
}

 

We also add a new Api Controller called "QuoteController" with the following code:

using System.Web.Http; 
using Codit.Blog.Waad.Mvc.Infrastructure.Auth;

namespace Codit.Blog.Waad.Mvc.Controllers.api 
{ 
    public class QuoteController : ApiController 
    { 
        [ApiAuthorize] 
        public string GetSecretQuote() 
        { 
            return "Housework can't kill you, but why take a chance?"; 
        } 
    } 
}

 

We implement our other buttons onclick event handlers:

function bttnCallClick() { 
    WinJS.xhr({ 
        url: 'http://localhost:50238/api/quote/GetSecretQuote', 
        type: 'GET' 
    }).then( 
        function (response) { 
            var log = document.getElementById("log"); 
            log.value = "bttnCall response \r\n"; 
            var json = JSON.parse(response.responseText); 
            log.value += json; 
        }, 
        function(error) { 
            var log = document.getElementById("log"); 
            log.value = "Error in call " + error; 
        }, 
        function (progress) { 
        } 
); 
}

Basically we're requesting the quote but as we're not logged in, our ApiAuthorization class will revoke authorization.

hit_mvc_without_jwt

 

function bttnTokenCallClick() { 
    WinJS.xhr({ 
        url: 'http://localhost:50238/api/quote/GetSecretQuote', 
        type: 'GET', 
        headers: { 
            "Authorization": "Bearer " + token 
        } 
    }).then( 
        function(response) { 
            var log = document.getElementById("log"); 
            log.value = "bttnCall response \r\n";

            var json = JSON.parse(response.responseText); 
            log.value += json; 
        }, 
        function(error) { 
            var log = document.getElementById("log"); 
            log.value = "Error in token call " + error; 
        }, 
        function(progress) { 
        } 
    ); 
};

 

Here we add the token to our Authorization header so it will get parsed in our Global.asax file.
hit_mvc_with_jwt

 

We could extend this example to add more in depth user role authentication but we simply need to call the GraphApi for this.

I hope you enjoyed this series. As you have already seen, there is a lot of configuration to do in order to configure ACS and WAAD. But, with a lot of trial and error you will get a better understanding on how things work.

Download part 2 of this series, the zip contains the Windows Store Application and an updated MVC Application.

Posted in: Azure | WIF

Tags: , ,


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