Handling REST SWT tokens in WCF WIF pipeline is not working - wcf

I'm struggling on a setup mentioned in the subject line and am wondering if someone can help me.
Essentially, what I have is a WCF service and I want to achieve that the user can authenticate against the ACS using a custom login page (using the javascript with required information from ACS).
After doing that the user should get redirected to the WCF service using the provided SWT token. I am using the SimpleWebTokenHandler as a basis for the SWT token handling, but I'm not sure it's playing any role in this.
Here's the Web.config I'm running
<configuration>
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral" />
</configSections>
...
<system.serviceModel>
<diagnostics>
</diagnostics>
<services>
<service name="WcfWifSwtAcs.Service1">
<endpoint address="xmlService" binding="webHttpBinding" bindingConfiguration="" behaviorConfiguration="restPoxBehaviour" name="xmlServiceEndpoint" contract="WcfWifSwtAcs.IService1" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="restPoxBehaviour">
<webHttp helpEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials useIdentityConfiguration="true">
...
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add scheme="http" binding="ws2007FederationHttpBinding" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<bindings>
<ws2007FederationHttpBinding>
<binding name="">
<security mode="Message">
<message
issuedTokenType="http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0">
<issuerMetadata address="https://xxxx.accesscontrol.windows.net/v2/wstrust/13/certificate/mex" />
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
...
</system.webServer>
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="http://localhost:56782/Service1.svc" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedIssuers>
<add thumbprint="XXX" name="xxx.accesscontrol.windows.net" />
</trustedIssuers>
</issuerNameRegistry>
<issuerTokenResolver type="SimpleWebToken.CustomIssuerTokenResolver, WcfWifSwtAcs" />
<securityTokenHandlers>
<clear/>
<add type="SimpleWebToken.SimpleWebTokenHandler, WcfWifSwtAcs"/>
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
</configuration>
Now I can see, that the authentication happens and that the browser is redirected with the body to the service. I can also see that the SimpleWebToken handler get's instantiated and the token type URI is being requested. But that's almost all that happens. No actual token handling verification and whatsoever is happnening.
This is the token that get's sent to the service (after parsing).
wa=wsignin1.0&
wresult=
<t:RequestSecurityTokenResponse
xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime>
<wsu:Created
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2013-02-13T23:14:30.159Z</wsu:Created>
<wsu:Expires
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2013-02-13T23:24:30.159Z</wsu:Expires>
</t:Lifetime>
<wsp:AppliesTo
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<EndpointReference
xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://localhost:56782/Service1.svc</Address>
</EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken>
<wsse:BinarySecurityToken
wsu:Id="uuid:58e2fb15-dd1a-40bd-8ff0-ae24e22e6efe"
ValueType="http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
BASE64 DATA==
</wsse:BinarySecurityToken>
</t:RequestedSecurityToken>
<t:TokenType>http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>
Service itself is braindead simple, with following signature.
[OperationContract]
[WebInvoke(UriTemplate = "/GetData/{id}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string GetData(string id);
Any ideas? I've been verifying that the uri's, hostnames, thumbprints etc. are all valid. Also the service tracing doesn't really show up anything that is related either to token handling or exceptions in the token verification.
Somehow it almost seems that the token doesn't get even passed to the handler. At least all the claims and other authentication information is missing (null).
I would appreciate if someone points me to a direction of either where can I debug or if I'm missing something really obvious (which might also always be the case).
P.S. I know I could achieve it with custom authentication modules and whatsoever, I'd rather get it running with WIF (it's becoming fundamental as I've spend more time on this as I really wanted and I'm very stubborn :p).

Soo, dedication will bring one to a solution. Although I initially thought that this can't be done, it's apparent that it actually can. I'll put the solution here, as maybe there are other people who find it useful.
First of all, WCF REST services are using webHttpBinding, which according to MS documentation does not support the Windows Identity Foundation and claims handling in the pipeline. Actually it does. Not in the WCF pipeline, but as the IIS module in web authentication flow.
First, you need to add the following modules to Web.config file.
<system.webServer>
<modules runManagedModulesForAllRequests="true">
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" preCondition="managedHandler" />
</modules>
</system.webServer>
There's a caveat tho. You need still the <configSections> from my original posting. The problem is that you need, in VisualStudio, to mark the System.IdentyModel* assemblies as CopyLocal items (in the properties window). Otherwise you'll get some cryptic exception that assembly cannot be loaded for the configuration section. NB! It only happens if you are loading these two modules and doesn't happen when those modules are not getting loaded. Didn't have any will to investigate that thing further, perhaps someone knows better what's the cause there.
Next if for any reason you plan to use the SWT token handling sample from MS WIF code, there are a couple of bugs that need to be fixed, otherwise the token parsing just won't happen or you will get invalid signatures out of the token verification.
SimpleWebToken.cs you need to fix the SwtBaseTime as it is initialized incorrectly and the security token creation fails afterwards:
From
public static DateTime SwtBaseTime = new DateTime( 1970, 1, 1, 0, 0, 0, 0 ); // per SWT psec
To
public static DateTime SwtBaseTime = new DateTime( 1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc ); // per SWT psec
SimpleWebTokenHandler.cs you need to fix the casing of the following values:
From
const string BinarySecurityToken = "binarySecurityToken";
const string ValueType = "valueType";
To
const string BinarySecurityToken = "BinarySecurityToken";
const string ValueType = "ValueType";
CustomIssuerTokenResolver.cs you need to fix the key that is created as it's initalized with a UTF8 bytes, but it should actually get initialized with decoded Base64 bytes:
From
key = new InMemorySymmetricSecurityKey(UTF8Encoding.UTF8.FromBase64String(base64Key));
To
key = new InMemorySymmetricSecurityKey(System.Convert.FromBase64String(base64Key));
After you've fixed all this, everything sits in place. The authenticators and authorizators are getting called and voilà, suddenly you have a WCF Service exposed as REST endpoint and all the claims etc. are also working.

I think your issue may be with the SWTTokenHandler in this sample: http://code.msdn.microsoft.com/vstudio/Custom-Token-ddce2f55
In CanReadToken(), it checks to see if the token is a BinarySecurityToken of type SWT:
if ( reader.IsStartElement( BinarySecurityToken )
&& ( reader.GetAttribute( ValueType ) == SimpleWebTokenConstants.ValueTypeUri ) )
But the constant BinarySecurityToken is defined as:
const string BinarySecurityToken = "binarySecurityToken";
Note the lower-case "b". XML elements are case sensitive, and the actual element is "BinarySecurityToken" with a capital B. This will cause the handler to return false in CanReadToken(), causing WIF to believe it doesn't have a handler registered for this token type.

Related

Adding Behavior Extension to PreCompiled Web Site

I'm trying to add the CORS headers to a WCF service which is part of a precompiled web site project in VS 2012.
The error
The type 'EnableCrossOriginResourceSharingBehavior, MyWebSite, Version=0.0.0.0, Culture=neutral' registered for extension 'crossOriginResourceSharingBehavior' could not be loaded.
from the config file
<behaviors>
<serviceBehaviors>...</serviceBehaviors>
<endpointBehaviors>
<behavior name="jsonBehavior">
<webHttp />
<crossOriginResourceSharingBehavior /> <!-- Error Here -->
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="crossOriginResourceSharingBehavior" type="EnableCrossOriginResourceSharingBehavior, MyWebSite, Version=0.0.0.0, Culture=neutral" />
</behaviorExtensions>
</extensions>
Now, there is no MyWebSite.dll in a precompiled site, apparently. So, how do I get past this and make the BehaviorExtension work?
You have that error because the definition has a wrong type: you lost namescpace of the type.
<add name="crossOriginResourceSharingBehavior" type="MyWebSite.EnableCrossOriginResourceSharingBehavior, MyWebSite, Version=0.0.0.0, Culture=neutral" />
Probably the version is wrong, because it equals to 0.0.0.0 in the definition. See AssemblyInfo.cs for the assembly version.
I see the assembly hasn't a strong name. So you can remove Version and Culture from the definition.
<add name="crossOriginResourceSharingBehavior" type="MyWebSite.EnableCrossOriginResourceSharingBehavior, MyWebSite" />

WCF: Custom RoleProvider with PrincipalPermissions: Doesn't appear to get hit / how to debug it?

I have a custom role provider that inherits off RoleProvider. In the web app this works fine with no problems.
However I am also using it in the WCF service and am having great problems stepping into it. to the extent that I suspect it isn't being hit at all. If I turn on any principal permissions at all I get access denied and the stack trace is totally unhelpful. even the WCF traces arnt really helpful in ascertaining what has happened.
I know that the TennisRoleProvider works off its default constructor and have verified its methods via test. It seems to be an integration issue.
So snippets ...
EDIT: I have since moved the role provider into the service assembly as read something about needing to involve GAK and keys (needs to run in full trust). I went down that path but things still didnt work so decided to simply move stuff into the service project to simplify. Still no joy.
<roleManager defaultProvider="TennisRoleProvider"
enabled="true"
>
<providers>
<clear/>
<add name="TennisRoleProvider"
type="Tennis.Security.TennisRoleProvider, Tennis.Security" />
</providers>
</roleManager>
<bindings>
<wsHttpBinding>
<binding name="wsHttpUserName">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
<bindings/>
<behavior name="RoleBehavior">
<serviceCredentials>
<serviceCertificate findValue="john-pc"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName"/>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="Tennis.Components.TennisUserValidator, Tennis.Components"/>
</serviceCredentials>
<serviceAuthorization principalPermissionMode="UseAspNetRoles"
roleProviderName="TennisRoleProvider">
</serviceAuthorization>
<serviceMetadata httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<errorHandler />
</behavior>
<services>
<service name="Tennis.Service.Services"
behaviorConfiguration="RoleBehavior">
<endpoint address="Family"
binding="wsHttpBinding"
bindingConfiguration="wsHttpUserName"
contract="Tennis.Service.Contracts.IFamilyAdmin"
/>
</service>
</services>
Then on the service method i have the following (Roles.Family admin is a string)
[PrincipalPermission(SecurityAction.Demand, Name = Roles.FamilyAdmin)]
public VoidResult<SuccessEnum> UpdateFamily(Family family)
{
}
so there are 2 questions ...
1) what have I done wrong?
2) How can I get into WCF to figure out exactly what is going wrong?
Cheers
The stack trace for the error in the logs is as follows
Note the permission in there is a different to the one I used above (Namley 'authorised' instead of 'FamilyAdmin'. However In the real thing those values match and the user has the correct permissions.
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
<System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
<EventID>131076</EventID>
<Type>3</Type>
<SubType Name="Warning">0</SubType>
<Level>4</Level>
<TimeCreated SystemTime="2012-06-29T12:45:30.2469191Z" />
<Source Name="System.ServiceModel" />
<Correlation ActivityID="{6e59b4f4-d59b-42eb-ad8e-4d5853f72900}" />
<Execution ProcessName="w3wp" ProcessID="9388" ThreadID="18" />
<Channel />
<Computer>JOHNN-PC</Computer>
</System>
<ApplicationData>
<TraceData>
<DataItem>
<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Warning">
<TraceIdentifier>http://msdn.microsoft.com/en-GB/library/System.ServiceModel.Diagnostics.TraceHandledException.aspx</TraceIdentifier>
<Description>Handling an exception.</Description>
<AppDomain>/LM/W3SVC/2/ROOT/Tennis-1-129854474506679191</AppDomain>
<Exception>
<ExceptionType>System.Security.SecurityException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message>Request for principal permission failed.</Message>
<StackTrace>
at System.Security.Permissions.PrincipalPermission.ThrowSecurityException()
at System.Security.Permissions.PrincipalPermission.Demand()
at System.Security.PermissionSet.DemandNonCAS()
at Nomical.Tennis.Service.Services.GetBookingsForUser(DateTime start, DateTime end) in c:\tfs\Tennis\TennisSolution\TennisCourts\Services.svc.cs:line 388
at SyncInvokeGetBookingsForUser(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
</StackTrace>
<ExceptionString>System.Security.SecurityException: Request for principal permission failed.
at System.Security.Permissions.PrincipalPermission.ThrowSecurityException()
at System.Security.Permissions.PrincipalPermission.Demand()
at System.Security.PermissionSet.DemandNonCAS()
at Tennis.Service.Services.GetBookingsForUser(DateTime start, DateTime end) in c:\tfs\Tennis\TennisSolution\TennisCourts\Services.svc.cs:line 388
at SyncInvokeGetBookingsForUser(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
The action that failed was:
Demand
The type of the first permission that failed was:
System.Security.Permissions.PrincipalPermission
The first permission that failed was:
<IPermission class="System.Security.Permissions.PrincipalPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1">
<Identity Authenticated="true"
ID="Authorised"/>
</IPermission>
The demand was for:
<IPermission class="System.Security.Permissions.PrincipalPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1">
<Identity Authenticated="true"
ID="Authorised"/>
</IPermission>
The assembly or AppDomain that failed was:
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionString>
</Exception>
</TraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
</E2ETraceEvent>
EDIT: Based on the answer below I added a few lines of code into the constructor - which whilst they don't achieve anything encouraged me to interrogate the thread static class.
EDIT: given a question about logs updated log to show that it is indeed from the log - or I am confused ;)
The TennisRoleProvider is referenced by it in its non public members - moreover when I overrode Name to make it return something I would recognise this was returned by it.
The problem was that I am using security across assemblies / app domains.
I need to make everything trusted and signed for it to work.
Put a break point in one of your role provider. If you WCF service is self hosted, start the host or if you have it under IIS just access its *svc file. From visual studio go to Debug, the click on Attach to Process and select your working process from the list w3wp.exe (if you are not able to see it select both check boxes "Show processes from all users" and "Show processes in all sessions". Now you can attach to your WCF service execution process. Last thing to do, from your client just call one of the test methods of your service and see if your break point gets a hit.
If that doesn't work, then just enable tracing at your service level and check the trace log files for any suspicious information.

WCF .NET 4 OutputCaching with Stream doesn't seem to work

I'm having problems with OutputCaching over a WCF REST service on .NET4-IIS7. My service has a custom Authorization scheme (by implementing ServiceAuthorizationManager), one which should take place on every request, and any caching must be done after the request is authorized. So far, this seems to work, only the caching part I can't see happening.
I have the following OperationContract:
[OperationContract]
[AspNetCacheProfile("PageZIP")]
[WebGet(UriTemplate = "pages/{page_id}")]
System.IO.Stream getPage(string page_id);
And the following web.config:
<system.web>
<compilation targetFramework="4.0" />
<customErrors mode="Off" />
<authentication mode="None" />
<caching>
<outputCache enableOutputCache="true"/>
<outputCacheSettings>
<outputCacheProfiles>
<add name="PageZIP" duration="7200" location="ServerAndClient"
varyByParam="page_id" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceAuthorization serviceAuthorizationManagerType="api.Authorization, api" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint transferMode="StreamedResponse" automaticFormatSelectionEnabled="false" defaultOutgoingResponseFormat="Json" />
</webHttpEndpoint>
</standardEndpoints></system.serviceModel>
I can see the new response headers filled with client-side cache information when I call the service, but caching at the server doesn't seem to work. My custom log shows that my api.Authorization class is (rightly) being called, but my getPage() method is also executing as normal, up to the point where my file is being .OpenRead() into a Stream and returned:
public System.IO.Stream getPage(string page_id) {
File zip = FileMapper.getZip(pid);
ctx.OutgoingResponse.Headers.Clear();
ctx.OutgoingResponse.Headers.Add("Content-Disposition", "attachment; filename=" + page_id + ".zip");
ctx.OutgoingResponse.Headers.Add("Content-Length", zip.size.ToString());
ctx.OutgoingResponse.ContentType = "application/zip";
Log.write("OpenRead", LogType.Info);
return System.IO.File.OpenRead(zip.path);
}
If output is being cached, this method shouldn't be executed at all... I expect the Stream to be cached and be served directly, without queries to the database and disk reads. Every zipfile is about 1 MB in size.
What am I missing or doing wrong?
After playing with settings for a while, the answer came out: OutputCache won't work if transferMode is streaming, even if you implement your own OutputCacheProvider. The reason behind this is that, in a streamed response scenario, you're telling WCF not to buffer your response in memory, and try to read it from wherever it is and send it down to transport level. OutputCache depends on the object being fully in-memory before it's returned, so that WCF can keep the reference to it and put that on cache.
It's up to you to see in your scenario if enabling streaming without output cache is faster than reading and keeping the object in-memory so you can output it directly.

Authenticating a WCF call from Silverlight using ADAM (AD LDS)

I have a Silverlight 4 client that invokes several WCF services. We want the communication to be encrypted using SSL (I have this part solved already) and that every call be authenticated against AD LDS (ADAM), do you have any simple example showing how to make this work? There's plenty of documentation on the oh-so-many WCF options but I haven't been able to find a simple working example of this particular (but I think very common) scenario (SSL encryption + ADAM authentication + Silverlight). Any help or pointers greatly appreciated.
You can use CustomUserNameValidator in WCF:
http://msdn.microsoft.com/en-us/library/aa702565.aspx
http://nayyeri.net/custom-username-and-password-authentication-in-wcf-3-5
and in Custom Validator's Validate Method you can query ADAM to authenticate user.
Regards.
Try this link on the Codeplex website, it seems like the setup and configuration for the scenario you've described. It provides a thorough checklist of all of the required settings:
Intranet – Web to Remote WCF Using Transport Security (Trusted Subsystem, HTTP)
If this isn't you exact scenario, take a look at the following section that may fill the gaps:
Application Scenarios (WCF Security)
The answer may depend on how you will handle permissioning, since you use ASP.net's membership provider for these functions.
If you want claims based authorization ADFS 1.0 (not 2.0) supports ADAM. If you want a STS that has more options try codplex's StarterSTS
If you want to use Role-Based Administration, try Enterprise Library from Microsoft P&P, the ASP.net membership provider, or direct COM access to Authorization manager (formerly known as AzMan)
I prefer & use the claims-based approach:
Use ActiveDirectory + ADFS 2.0
(why not use the built in fault tolerance and replication of AD?, ADAM is a pain)
Silverlight Implementation of Ws-Trust as documented here:
http://www.leastprivilege.com/UsingSilverlightToAccessWIFSecuredWCFServices.aspx
Edgar, I'm also interested in any results you had, I am at the same place you were in.
Shoaib, I have looked at this but I think it is less desirable than using just the .config via ActiveDirectoryMembershipProvider, as then you are just using off the shelf components, not writing your own security system.
EDIT:
I hope this helps someone. I can't belive there is not a good example of this on the internet. It is simple enough. As I said before this is superior to using a custom authentication system.
Using AD LDS (ADAM) authentication with Silverlight compatible WCF calls (non wsHttp)
Client side:
1) Invocations from Silverlight look like this, this works if you are using the Channel Factory too.
var client = new MyWCFServiceClient();
client.GetPersonCompleted += client_GetPersonCompleted;
client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;
client.GetPersonAsync();
2) return values from server will have an Error property if the login fails. If the user lookup fails, the error is something confusing like “at least one security token could not be validated”. Since your server side code is not able to re-wrap this (as it's all happening in the web.config) it is better for your client code to catch System.ServiceModel.Security.MessageSecurityException and interpret it as a login failure, or check the InnerException message to make sure it is the "security token" thing.
void client_GetPersonCompleted(object sender, GetPersonCompletedEventArgs e)
{
if (e.Error == null)
{
// do stuff with e.Result;
}
if (e.Error is MessageSecurityException)
{
// Your login did not work
}
}
Server side:
1) The WCF service class must have
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] or Required
2) you have to set up your LDS instance with SSL enabled, which is tricky. See: h't't'p://erlend.oftedal.no/blog/?blogid=7
3) web config - need to:
Add the LDS connection string
Add ActiveDirectoryMembershipProvider
Add <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
change your custom binding to include <security authenticationMode="UserNameOverTransport"/> see http://msdn.microsoft.com/en-us/library/dd833059(VS.95).aspx
Example:
<configuration>
<connectionStrings>
<add name="ADConnectionString" connectionString="LDAP://myserver:[SSL port number]/[where your user are in LDS, in my case: ‘OU=ADAM Users,O=Microsoft,C=US’]" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<membership defaultProvider="MyActiveDirectoryMembershipProvider">
<providers>
<add
name="MyActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
connectionUsername="[domain]\[username]"
connectionPassword="[plain text windows password]"
connectionProtection="Secure"
/>
</providers>
</membership>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehaviour">
<serviceMetadata
httpsGetEnabled="true"
httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="MembershipProvider"
membershipProviderName="MyActiveDirectoryMembershipProvider"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="myCustomBinding">
<security authenticationMode="UserNameOverTransport"/>
<!-- <binaryMessageEncoding /> this is optional, but good for performance-->
<httpsTransport />
</binding>
</customBinding>
</bindings>
<services>
<service name="MessageBasedSecurity.Web.MyWCFService" behaviorConfiguration="MyServiceBehaviour">
<endpoint address="" binding="customBinding" bindingConfiguration="myCustomBinding"
contract="MessageBasedSecurity.Web.MyWCFService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
I hope this helps someone. I welcome comments/improvements.

How can you hide the fact your server has a WCF service located at MyService.svc

Is there a way to serve up a custom "Sorry not found" page from a direct access request to a WCF Service (.svc file) on a server running IIS 6.0, and .NET 3.5 SP1.
I have a requirement that my service in a Production environment is not discoverable. The requirement states that WSDL publishing should be off, and the request also states that when directly accessing the MyService.svc file via a HTTP Get Request that a "Sorry Not found" page is displayed instead.
I have no problem disabling the metadata in the config file.
<serviceMetadata httpGetEnabled="false" />
But I can't figure out a way to not show the default .svc page.
SERVICE
This is a Windows© Communication Foundation service.
Metadata publishing for this service is currently disabled.
If you have access to the service, you can enable metadata publishing by completing the following steps to modify your web or application configuration file:
...
** Also posted at ServerFault.
in web.config:
<httpHandlers>
<remove verb="*" path="*.svc" />
<add path="*.svc" verb="POST" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
</httpHandlers>
Try setting http[s]HelpPageEnabled to false in Web.config. Example:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="false" />
<serviceDebug httpHelpPageEnabled="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>