Integrating WIF, WF 4.0 & AppFabric: Claims-Based-Delegation
Posted by zamd on June 3, 2010
Extending upon my last post, where I have talked about basic integration, this post will go into the details of claims-based-delegation in WF 4.0 & AppFabric.
Again I’ll be using activities from the Workflow Security Pack. Let’s start with a diagram which captures the main components of solution and their interactions:
- Unauthenticated user browse to web application protected by WIF modules.
- WIF redirects user to Passive STS for authentication
- User authenticates @ passive STS and is redirected back to the ASP.net app along with the Issued Token. WIF modules processes this token and upon successful validation of the token, user is logged into the application. I have configured SaveBootstrapTokens on this app, so the raw incoming token is preserved as part of IClaimsIdentity.
- Users click the “Call Service” link to invoke following client workflow. GetBootstrapToken activity reads the bootstrap token and enlist it with the SecurityTokenHandle (specified on the InitializeActAsToken activity) as an ActAs token.
- InitializeSamlSecurityToken activity issues a request (RST) to acquire a SAML token from the STS using the ActAs token enlisted in step 4. InitializeSamlSecurityToken is able to see the ActAs token (enlisted by InitializeActAsToken activity) because both of these activities share the same SecurityTokenHandle. At this stage, Web App is authenticated by STS using windows authentication while ActAs token is a Saml token (acquired using forms authentication in step 3). The final Saml token will contain claims for both immediate (web app) and original caller (authenticated user).
- TokenFlowScope activity along with the workflowCredentials behaviour (configured on the endpoint used by the Echo activity) enhances the WCF security pipeline to attach the Saml token (acquired in step 5) with the outgoing message. As part of it’s execution, TokenFlowScope will detect that Echo activity requires a Saml token, it will then check it’s enlisted tokens to see if it can satisfy the token requirements of the Echo activity. In this example, there is already a Saml token enlisted with the handle so TokenFlowScope simply attaches that token with the outgoing message.
I have attached complete solution with this post. I tried to keep the solution self-contained by using file-based certificates and other shortcuts so hopefully you should be able to get it working by just hitting Ctrl-F5 🙂
Feel free to download and experiment and let me know your thoughts…
scott_m said
Great post and sample.
I did have an issue though when I ran on my box (VS2010 / Win 2008 R2). I get the following error after clicking the submit button the login page:
System.Exception was unhandled by user code
Message=An unexpected error occurred when processing the request. See inner exception for details.
Source=App_Web_fjtq1aid
StackTrace:
at _Default.Page_PreRender(Object sender, EventArgs e) in c:\Dev\CSharp\WIF\WF-Claims-based-delegation\MvcApplication2_STS\Default.aspx.cs:line 69
at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
at System.Web.UI.Control.OnPreRender(EventArgs e)
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
InnerException: System.InvalidOperationException
Message=The action ” (Request.QueryString[‘wa’]) is unexpected. Expected actions are: ‘wsignin1.0’ or ‘wsignout1.0’.
Source=App_Web_fjtq1aid
StackTrace:
at _Default.Page_PreRender(Object sender, EventArgs e) in c:\Dev\CSharp\WIF\WF-Claims-based-delegation\MvcApplication2_STS\Default.aspx.cs:line 58
InnerException:
Looks like it was from:
MVCApplication2/Default.aspx
.
.
.
catch ( Exception exception )
{
throw new Exception( “An unexpected error occurred when processing the request. See inner exception for details.”, exception );
}
zamd said
Try running STS seperately and then run the Relying party app seperately and it should work.
scott_m said
Got a little further. I set the mvcapplication2_sts to not auto start and wait for an external app to attach.
Now when this code executes for this first time:
“var output = WorkflowInvoker.Invoke(new CallerService());”
I get:
“System.TimeoutException was unhandled by user code
Message=Client is unable to finish the security negotiation within the configured timeout (00:00:59.9970000). The current negotiation leg is 1 (00:00:59.9960000).
Source=System.Activities
StackTrace:
at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
at MvcApplication2.Controllers.ServiceController.Call() in C:\Dev\CSharp\WIF\WF-Claims-based-delegation\MvcApplication2\Controllers\ServiceController.cs:line 23
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.c__DisplayClassd.b__a()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
InnerException: System.TimeoutException
Message=The request channel timed out while waiting for a reply after 00:00:59.9850000. Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding. The time allotted to this operation may have been a portion of a longer timeout.
Source=System.ServiceModel
StackTrace:
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)
InnerException: System.TimeoutException
Message=The HTTP request to ‘http://localhost:9000/STS/Trust13’ has exceeded the allotted timeout of 00:00:59.9900000. The time allotted to this operation may have been a portion of a longer timeout.
Source=System.ServiceModel
StackTrace:
at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
InnerException: System.Net.WebException
Message=The operation has timed out
Source=System
StackTrace:
at System.Net.HttpWebRequest.GetResponse()
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
InnerException:
”
If I run the same code a second time, it works great.
zamd said
Good to see, you got it working. Not sure, why it doesn’t work the first time.
scott_m said
Thats pretty cool how you use your CallService.xaml as a facade for calling the back-end wf service. In the example, the Echo WF service appears to be configured (URL / bindings) via app.config. In my case the actual WF service url will not be known until runtime. Is there a way to dynamically set the WF Service URL for the client?
thanks
zamd said
Unfortunately “Add Service Reference” generated activities (like the Echo activity in sample) doesn’t allow you to change the endpoint address dynamically. You would have to use plain Send/ReceiveReply activities for this.
scott_m said
I was looking at the TokenFlowHandler used by the TokenFlowScope. From a high level, looks like it uses the TokensExtension to provide the hook to inject the W.I.F. token into the message prior to actual transmission. I noticed the TokensExtension Atach/Detach interface methods were empty but there was a method called “FindToken” that looked like it did something. Looks like the GenericSecurityTokenProvider is responsible for calling “FindToken”. How does GenericSecurityTokenProvider get wired into the WCF pipeline?
thanks
Alejandro said
Hello Zamd,
We are implementing a scenario similar to what you describe. We have several Azure hosted WCF services which trust a local active STS, and a web application which trusts a local passive STS.
The latter STS has a trust relationship with LiveID. This part of stage is working properly. The user tries to access the web application, it forwards the local STS, which returns to redirect to LiveID. Once the user logs in, he is redirected to the local STS again, with the Claim of Live, which transforms it and gets the claims our web site requires, and redirects the user back to the web site. When the application needs to invoke a service, it use CreateChannelActingAs with Bootstrap token, in order to copy the tokens in the local Active STS and then perform the service invocation.
Furthermore, we need to invoke the services from a desktop application. To implement this login to Live as follows:
var issuer = “https://dev.login.live-int.com/wstlogin.srf”;
var binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = false;
WSTrustChannelFactory fact = new WSTrustChannelFactory(binding, new EndpointAddress(issuer));
fact.TrustVersion = TrustVersion.WSTrustFeb2005;//.WSTrust13;
var unp = fact.Credentials.UserName;
unp.UserName = usuario;
unp.Password = password;
var client = fact.CreateChannel();
var rst = new RequestSecurityToken(WSTrustFeb2005Constants.RequestTypes.Issue);
rst.AppliesTo = new EndpointAddress(“http://live-int.com”);
RequestSecurityTokenResponse rstr;
SecurityToken bootstrapToken = client.Issue(rst, out rstr);
After the user enters Live and get the token we want to invoke the services.
ChannelFactory factory = new ChannelFactory(“CustomBinding_ISecSpot”);
factory.Credentials.ServiceCertificate.SetDefaultCertificate(“CN=localhost”, StoreLocation.LocalMachine, StoreName.My);
factory.ConfigureChannelFactory();
ISecSpotChannel channel = factory.CreateChannelActingAs(bootstrapToken);
channel.MyMethod()
This is where we face the problem as trying to go through the local Active STS, we get an error.
Is this the correct way? Is there any way to resolve what we are trying?
Thks, Alejandro
zamd said
Hi,
I don’t believe https://dev.login.live-int.com/wstlogin.srf endpoint is fully functional and supports your active/WS-Trust scenario.
Last I looked, there was no public way to register a Relying party (in your case your local STS) with LiveID STS. Even though you get back a token but it is encrypted & meant for some internal LiveID properties and NOT your STS/RP.
HTH,
Zulfiqar
2010 in review « Zulfiqar's weblog said
[…] Integrating WIF, WF 4.0 & AppFabric: Claims-Based-Delegation June 2010 9 comments 5 […]