Exercise 4: Invoking a WCF Service on the Backend via Delegated Access
One common practice in ASP.NET development is user impersonation. A website may need to access resources with the privileges of the incoming user, hence there are mechanisms in place that allow the application itself to operate under the current user’s identity. One possible issue with this approach is that it offers a large attack surface: a successful hack of the application will lead to acquiring the user’s privileges no matter which part of the application was subverted.
Windows Identity Foundation provides the means for mitigating this, by allowing an ASP.NET application to choose when to act as the current website user in delegated invocations and when to use its own application identity regardless of who the current user is (hence behaving as a trusted subsystem).
From the architectural point of view, Windows Identity Foundation achieves this by leveraging the ActAs mechanisms defined in the WS-Trust protocol. The ASP.NET application’s code behind requires a token to an STS using its own application credentials, however it also attaches to the request the token that the current user sent in order to authenticate with the website: the STS processes the request and issue a delegated token, which in turn the ASP.NET application uses for invoking a web service acting as the website user.
From the practical point of view, this means that the ASP.NET developer needs to follow few extra steps before calling the backend service he needs. In the following exercise we will demonstrate exactly that, showing how to augment the solution we built in the former exercises with a delegated call to a backend service.
.png)
Figure 44The Delegated Access scenario implemented in exercise 4
Task 1 - Inspecting the Beginning Solution
- Open Microsoft Visual Studio 2010 with administrator privileges. From Start | All Programs | Microsoft Visual Studio 2010, right-click on Microsoft Visual Studio 2010 and select Run as administrator.
- Open the ClaimsEnableWebSite.sln solution file located in the %YourInstallationFolder%\Labs\WebSitesAndIdentity\Source\Ex4-InvokingViaDelegatedAccess\Begin folder.
- On the Solution Explorer examine the projects that compose the beginning solution.
- https://localhost/ClaimsEnableWebSiteEx04: a simple relying party ASP.NET application.
- https://localhost/ClaimsEnableWebSiteEx04_STS: a simple local STS acting as identity provider for the relying party.
- http://localhost/ActAsSts: a pre-configured STS capable of issuing delegated tokens.
- RevenuesService: a WCF service pre-configured to authorize access to the RevenuesOfTheDay operation for users under the Manager role.
.png)
Figure 45Starting solution
If you want to test the begin solution start the RevenuesService before browsing the relying party site (https://localhost/ClaimsEnableWebSiteEx04) and use john/p@ssw0rd or paul/p@ssw0rd credentials.
- Open the Default.aspx Web page from the relying party (https://localhost/ClaimsEnableWebSiteEx04).
- Add a Label control to the Default.aspx webpage with the ID lblResults.
<div>
<p>
Welcome <asp:Label ID="txtName" runat="server" Text=""></asp:Label>.
</p>
...
<p>
<asp:Label ID="lblResults" runat="server" Text=""></asp:Label>
</p>
<asp:HyperLink ID="hlLink" runat="server" NavigateUrl="~/SecretPage.aspx">Go to secret page</asp:HyperLink>
</div>
Task 2 - Configuring the Client in the Relying Party to Access the RevenuesService
- On the Solution Explorer right-click on the RevenuesService project and select Set as StartUp project.
- Go to the Debug menu and select Start Without Debugging.
- Go back to the Visual Studio and, on the Solution Explorer right-click on the https://localhost/ClaimsEnableWebSiteEx04/ project and select Add Service Reference.
- Enter http://localhost:6003/RevenuesService in the Address textbox and click OK.
.png)
Figure 46Add Service Reference dialog
- Close the RevenuesService console.
.png)
Figure 47RevenuesService console
- Open the web.config file of the https://localhost/ClaimsEnableWebSiteEx04/ project.
- The configuration generated is missing the binding of the ActAs STS service. Add the binding and bindingConfiguration attributes to the issuer configuration setting (system.serviceModel/bindings/customBinding/binding/security/issuedTokenParameters/issuer).
(Code Snippet – Web Sites And Identity Lab - Ex04 Issuer Binding Properties)
<bindings>
<customBinding>
<binding name="CustomBinding_IRevenuesService">
<security defaultAlgorithmSuite="Default" ...>
<issuedTokenParameters keyType="SymmetricKey"...>
...
<issuer address="http://localhost/ActAsSTS/Issue.svc"
binding="ws2007HttpBinding" bindingConfiguration="ActAsBinding"
/>
<issuerMetadata address="http://localhost/ActAsSTS/Issue.svc/mex" />
</issuedTokenParameters>
...
</security>
...
</binding>
</customBinding>
</bindings>
- Add the following binding to the bindings section.
(Code Snippet – Web Sites And Identity Lab - Ex04 ActAs Binding Settings)
<system.serviceModel>
...
<bindings>
...
</customBinding>
<ws2007HttpBinding>
<binding name="ActAsBinding">
<security mode="Message">
<message establishSecurityContext="false" />
</security>
</binding>
</ws2007HttpBinding>
</bindings>
<client>
...
</client>
</system.serviceModel>
from now on you can call the RevenuesService from the Website relying party delegating the identity via the ActAs STS.
Task 3 - Calling the Service
- Open the Default.aspx.cs code behind from the relying party (https://localhost/ClaimsEnableWebSiteEx04/).
- Add the following using statements.
(Code Snippet – Web Sites And Identity Lab - Ex04 Default Page Usings)
using System.Collections.ObjectModel;
using System.Globalization;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
using Microsoft.IdentityModel.Protocols.WSTrust;
using ServiceReference1;
- Insert the following code that will call the RevenuesService inside the _Default class and immediately below under the Page_Load event handler.
(Code Snippet – Web Sites And Identity Lab - Ex04 Call Revenue Service With ActAs)
private string GetRevenueFromBackendService()
{
SecurityToken bootstrapToken = ((IClaimsPrincipal)Thread.CurrentPrincipal).Identities[0].BootstrapToken;
string tmpResult = "Call failed";
if (bootstrapToken == null)
{
// We lost the session state but the user still has the federated ticket
// Let's sign the user off and start again
Response.Redirect(Request.Url.AbsoluteUri);
return tmpResult;
}
// Get the channel factory to the backend service from the application state
ChannelFactory<IRevenuesServiceChannel> factory = new ChannelFactory<IRevenuesServiceChannel>("CustomBinding_IRevenuesService");
factory.Credentials.ServiceCertificate.SetDefaultCertificate("CN=localhost", StoreLocation.LocalMachine, StoreName.My);
factory.ConfigureChannelFactory();
// Create and setup channel to talk to the backend service
IRevenuesServiceChannel channel;
// Setup the ActAs to point to the caller's token so that we perform a delegated call to the backend service
// on behalf of the original caller.
channel = factory.CreateChannelActingAs<IRevenuesServiceChannel>(bootstrapToken);
// Call the backend service and handle the possible exceptions
try
{
tmpResult = string.Format(
CultureInfo.InvariantCulture,
"Revenues of the day: $ {0:0.00}",
channel.RevenuesOfTheDay());
channel.Close();
}
catch (SecurityAccessDeniedException)
{
channel.Abort();
tmpResult = "Access is denied";
}
catch (CommunicationException exception)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(exception.Message);
sb.AppendLine(exception.StackTrace);
Exception ex = exception.InnerException;
while (ex != null)
{
sb.AppendLine("===========================");
sb.AppendLine(ex.Message);
sb.AppendLine(ex.StackTrace);
ex = ex.InnerException;
}
channel.Abort();
tmpResult = sb.ToString(); ;
}
catch (TimeoutException)
{
channel.Abort();
tmpResult = "Timed out...";
}
catch (Exception exception)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("An unexpected exception occured.");
sb.AppendLine(exception.StackTrace);
channel.Abort();
tmpResult = sb.ToString();
}
return tmpResult;
}
There are various things happening in this code: in the average case you will probably just cut & paste and change just the name of the services involved, however it is worth to understand what the code does.
First, you are extracting the so called “bootstrap token” from the
ClaimsPrincipal. This is the token that the ASP.NET application received from the identity provider at sign in time.
Once you obtained the bootstrap token, you need to inject it in the web service call so that it will be used (along with the ASP.NET application identity credentials) for obtaining a token from the ActAs STS, which in turn will be used for invoking the desired service. The mechanism provided by Windows Identity Foundation for implementing the above is the
CreateChannelActingAs extension method.
- Open the web.config file of the https://localhost/ClaimsEnableWebSiteEx04 project. Locate the service element in microsoft.identityModel section, insert a new attribute saveBootstrapTokens and set its value to true.
.png)
Figure 48Configuring the availability of the bootstrap token
- In Default.aspx.cs file of the https://localhost/ClaimsEnableWebSiteEx04 project, add the following code in the Page_Load event handler to show the result on a label of the Web page.
protected void Page_Load(object sender, EventArgs e)
{ this.txtName.Text = User.Identity.Name;
IClaimsIdentity claimsIdentity = ((IClaimsPrincipal)(Thread.CurrentPrincipal)).Identities[0];
DateTime birthDay = DateTime.Parse(
(from c in claimsIdentity.Claims
where c.ClaimType == System.IdentityModel.Claims.ClaimTypes.DateOfBirth
select c.Value).FirstOrDefault());
int days = (birthDay.DayOfYear - DateTime.Today.DayOfYear);
this.txtDaysToBday.Text =
((days < 0) ? days + 365 + (DateTime.IsLeapYear(DateTime.Today.Year) ? 1 : 0) : days).ToString();
this.lblResults.Text = GetRevenueFromBackendService();
}
Exercise 4: Verification
In order to verify that you have correctly performed all steps in exercise four, proceed as follows:
- Run the RevenueService service. To do this, on the Solution Explorer, right-click the RevenueService and select Debug | Start new instance.
.png)
Figure 49RevenuesService running
- Open an Internet Explorer window and navigate to https://localhost/ClaimsEnableWebSiteEx04.
.png)
- Log in using John's credentials (username: john, password: p@ssw0rd).
.png)
Figure 51Logged in using John's credentials
Since John is not in Manager role, he cannot access the backed service which implements a ClaimsAuthorizationManager to restrict access to Manager users only.
If you get an exception when calling the service, it could be because you don’t have installed Windows Communication Foundation. To install the missing components execute the following command:
"\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\ServiceModelReg.exe" -i - Open the RevenuesService host console and notice the information displayed.
.png)
Figure 52Service host showing the token containing the user and the ActAs user
- Close the browser window and open a new browser instance.
- Navigate to https://localhost/ClaimsEnableWebSiteEx04 and log in using Paul credentials which is in Manager role (username: paul, password: p@ssw0rd).
.png)
Figure 53Logged in using Paul's credentials