Thread.CurrentPrincipal cannot be set to Forms Authentication principal - asp.net

I have a WCF service, which is hosted inside of an ASP.NET MVC application (as described in http://msdn.microsoft.com/en-us/library/aa702682.aspx). Part of the MVC actions and WCF service operations are protected, and I use ASP.NET Forms Authentication for both:
// protected MVC action
[Authorize]
public ActionResult ProtectedMvcAction(string args)
// protected WCF operation
[PrincipalPermission(SecurityAction.Demand, Role = "User")]
public void ProtectedWcfOperation(string args)
My WCF client makes sure that the Forms Authentication .ASPXAUTH cookie gets transmitted to the server on every WCF call.
This worked very well for a long time. Now I'm adding HTTPS encryption to my server using an SSL certificate. This required me to make the following changes to the Web.config`:
<basicHttpBinding>
<binding name="ApiServiceBinding">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
</basicHttpBinding>
The service gets activated and the client can invoke the server operations. However, the [PrincipalPermission] attribute in front of the protected server operations suddenly blocks all service calls. I found out the following:
In the HTTP case (without <security mode="Transport">), both Thread.CurrentPrincipal and HttpContext.Current.User are set to a RolePrincipal instance, with a FormsIdentity instance in the RolePrincipal.Identity property. In this case, everything works fine.
In the HTTPS case (with <security mode="Transport"> in the web.config), the property HttpContext.Current.User is still set to the RolePrincipal/FormsIdentity combination. But, the property Thread.CurrentPrincipal is suddenly set to WindowsPrincipal/WindowsIdentity instances, which makes the [PrincipalPermission] attribute throw an exception.
I tried the following:
Changed the AppDomain.CurrentDomain.SetPrincipalPolicy to every possible value (in Global.asax's Application_Start), but that did not change anything.
Set the property Thread.CurrentPrincipal in Application_PostAuthenticate, but between Application_PostAuthenticate and the actual service invoke, the Thread's CurrentPrincipal is changed to a WindowsPrincipal again.
Any hints? What am I doing wrong?

This solves it:
http://www.codeproject.com/Articles/304877/WCF-REST-4-0-Authorization-with-From-Based-authent
I mofied this code to cover Windows and Forms endpoints and the same service - which also works -
public bool Evaluate( EvaluationContext evaluationContext, ref object state )
{
bool ret = false;
// get the authenticated client identity
HttpCookie formsAuth = HttpContext.Current.Request.Cookies[ ".MyFormsCookie" ];
if( null != formsAuth )
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt( formsAuth.Value );
if( null != ticket )
{
GenericIdentity client = new GenericIdentity( ticket.Name, "Forms" );
// set the custom principal
CustomPrincipal p = new CustomPrincipal( client );
p.RoleManagerProvider = "Internet";
evaluationContext.Properties[ "Principal" ] = p;
ret = true;
}
}
else
{
CustomPrincipal p = new CustomPrincipal( HttpContext.Current.User.Identity );
p.RoleManagerProvider = "Intranet";
evaluationContext.Properties[ "Principal" ] = p;
// assume windows auth
ret = true;
}
return ret;
}
Which looks for the forms auth cookie and tries to use windows authentication if its not there. I also "flip" the role provider for internal and external
This allows me to propogate the users credentials from an internet (by forwarding the cookie) and intranet (using windows constrained delegation) to the same internal service.
I did the config in config (rather than code as per sample) and it seems fine.
For the behaviour its something like:
<behavior name="FormsPaymentsBehavior">
<serviceAuthorization principalPermissionMode="Custom" >
<authorizationPolicies>
<add policyType="FormsPolicy.AuthorizationPolicy,FormsPolicy" />
</authorizationPolicies>
</serviceAuthorization>
This is used for both endpoints as the FormsPolicy (above) handle both and you cannot specify different behaviours for different endpoints.
The bindings enforce the windows credentials handshake on the appropriate endpoint:
<basicHttpBinding>
<binding name="WindowsHttpBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
<binding name="FormsHttpBinding" allowCookies="true">
<security mode="None">
<transport clientCredentialType="None" />
</security>
</binding>
</basicHttpBinding>
The transport mode can be changed to
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
For https and it works fine.
For your custom principal I found I had to explicitly call the role manager
...
public bool IsInRole( string role )
{
RoleProvider p = Roles.Providers[ RoleManagerProvider ];
return p.IsUserInRole( Identity.Name, role );
}
public String RoleManagerProvider { get; set; }
...
This is, I guess, because I was no longer using any of the aspnet compat stuff. Since I am flipping role manager depending on my authentication type then ho-hum.

I have experienced this issue too and there is another report (and mine) here. http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/8f424d4f-2f47-4f85-a6b0-00f7e58871f1/
This thread points to the correct solution to be to create a custom Authorisation Policy (http://msdn.microsoft.com/en-us/library/ms729794.aspx) and this code project article (http://www.codeproject.com/Articles/304877/WCF-REST-4-0-Authorization-with-From-Based-authent) seems to explain exactly how to do this for FormsAuth - setting the evaluationContext.Properties["Principal"] = new CustomPrincipal(client) as per MS comments.
I have not yet implemented this - my "quick fix" was to simply revert to a plain old asmx service - but I will be "giving it a go" some time!
If you find another solution - please let me know.

Related

WCF authentication performance?

In my WCF service implementation I used username/password for authentication, with ASP.NET Identity 2.0 and with the following codes.
public class IdentityValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
Debug.WriteLine("Validate "+userName);
using (var context = new ApplicationDbContext())
using (var userStore = new UserStore<ApplicationUser>(context))
using (var userManager = new UserManager<ApplicationUser>(userStore))
{
var user = userManager.Find(userName, password);
if (user == null)
{
var msg = String.Format("Unknown Username {0} or incorrect password {1}", userName, password);
Trace.TraceWarning(msg);
throw new FaultException(msg);//the client actually will receive MessageSecurityException. But if I throw MessageSecurityException, the runtime will give FaultException to client without clear message.
}
}
Debug.WriteLine("End validate " + userName);
}
}
and the config is like
<behavior name="authBehavior">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="IdentityValidator,Security" />
</serviceCredentials>
<serviceAuthorization principalPermissionMode="Custom" serviceAuthorizationManagerType="RoleAuthorizationManager,Security"></serviceAuthorization>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="True" />
</behavior>
The functionality of authentication is so far so good. And the client is typically a Windows desktop app that will persist the user name and password all the time.
I understand the WCF authentication is per message. That means each call will run though some SQL queries against the database that stores the user name and password hash. So far in my low traffic test, the performance is good enough, say the authentication could be done within 1 second.
However, under heavy traffic, I wonder if it is a good idea to cache the result of querying the database, for example, before running userManager.Find, check if the userName and password pair had been authenticated in MemoryCache with 10 minutes expiry.
Is this something micro performance optimization? or in your experiences, did you need to be concerned by the performance of authentication of WCF services, built-in or custom?

Adyen web service access with .Net

I am trying to implement Adyen recurring payment to my web application (C# .Net 4) but being relatively new to web services I am not sure I am doing it the right way.
In short the payment provider exposes a WSDL url for that purpose (https://pal-test.adyen.com/pal/Recurring.wsdl) and I added it in Visual Studio 2010 as a Service Reference (i.e. Add Service Reference > Advanced > Add Web Reference)
I then went on and created a test page, to make sure the connection was operational (see code below) and retrieve the details of a test subscription that I created previously. However I am getting an exception when executing the 'listRecurringDetails' action with the error message is 'Object reference not set to an instance of an object." and I cannot figure where I am going wrong.
Any feedback would be welcome.
#
public partial class Store_ServiceTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Recurring proxy = new Recurring();
ICredentials usrCreds = new NetworkCredential("[username]", "[password]");
proxy.Credentials = usrCreds;
try
{
RecurringDetailsRequest thisUserDetail = new RecurringDetailsRequest();
thisUserDetail.merchantAccount = "[some reference]";
thisUserDetail.shopperReference = "[some reference]";
thisUserDetail.recurring.contract = "RECURRING";
RecurringDetailsResult recContractDetails = proxy.listRecurringDetails(thisUserDetail);
string createDate = recContractDetails.creationDate.ToString();
}
catch (Exception ex)
{
string err = ex.Message;
}
finally
{
proxy.Dispose();
}
}
}
Call Stack
App_Web_4h0noljo.dll!Store_ServiceTest.Page_Load(object sender, System.EventArgs e) Line 38 C#
Output window
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
An exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll but was not handled in user code
A first chance exception of type 'System.NullReferenceException' occurred in App_Web_4h0noljo.dll
The thread '' (0x15d0) has exited with code 0 (0x0).
Your code looks good. The key is to add the Recurring service as a
Service Reference instead of as a Web Reference. It should work if the
app configuration file contains:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="AdyenHttpBinding">
<security mode="Transport">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="Basic" realm="Adyen PAL Service Authentication"> <!--Adyen PAL Service Authentication-->
<extendedProtectionPolicy policyEnforcement="Never"/>
</transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://pal-test.adyen.com/pal/servlet/soap/Payment" binding="basicHttpBinding" bindingConfiguration="AdyenHttpBinding" contract="Adyen.Payment.PaymentPortType" name="PaymentHttpPort"/>
<endpoint address="https://pal-test.adyen.com/pal/servlet/soap/Recurring" binding="basicHttpBinding" bindingConfiguration="AdyenHttpBinding" contract="Adyen.Recurring.RecurringPortType" name="RecurringHttpPort"/>
</client>
</system.serviceModel>
Kind Regards,
Sander Rasker (Adyen)

Hosting an authentication-free WCF REST service side-by-side with ASP.NET with Forms Authentication

I would like to host a REST-full WCF 4.0 service on an already-created IIS 7.5 website based on ASP.NET v4.0 and secured by forms authentication. So, I tried to configure my WCF stack using mixed mode authentication (aspNetCompatibilityEnabled="false") and configured the service host to use no security at all but So long, all my efforts was completely unsuccessful. When I try to call my service from the browser after a while the connection to the host is closed without a response and my browser raises an error indicating the connection to the target webstie is closed without any response.
However, if I write a dummy code in Application_BeginRequest to authenticate a dummy user in forms authentication module using FormsAuthentication.Authenticate or call the service in an authenticated browser session everything works fine and the service is called successfully.
I tried to find the problem causing this strange behavior using WCF tracing. What I have found from the resulting svclog file is this exception:
Message: Object reference not set to an instance of an object.
StackTrace:
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.get_LogonUserIdentity()
System.ServiceModel.Channels.HttpChannelListener.ValidateAuthentication(IHttpAuthenticationContext authenticationContext)
System.ServiceModel.Channels.HttpRequestContext.ProcessAuthentication()
System.ServiceModel.Channels.HttpChannelListener`1.HttpContextReceived(HttpRequestContext context, Action callback)
Any idea about the problem?
UPDATE: I even set the authentication mode of the website to "None" and authorized anonymous users. Still the same results. Nothing changed. The question is that can I use unauthenticated WCF RESTfull services with aspNetCompatibilityEnabled="false" on an ASP.NET website at all???
To be more specific, What I have tried to do is:
Implemented my WCF service in the form of a .svc file
Configured WCF in my web.config file as the following (note AspNetCompatibilityEnabled="false"):
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="false" />
</system.serviceModel>
Created and used my own ServiceHostFactory like this:
public class MyServiceHostFactory : ServiceHostFactoryBase
{
#region Methods
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var type = Type.GetType(constructorString);
var host = new WebServiceHost(type, baseAddresses);
var serviceBehavior = host.Description.Behaviors.OfType<ServiceBehaviorAttribute>().Single();
serviceBehavior.ConcurrencyMode = ConcurrencyMode.Multiple;
serviceBehavior.MaxItemsInObjectGraph = int.MaxValue;
var metadataBehavior = host.Description.Behaviors.OfType<ServiceMetadataBehavior>().SingleOrDefault();
if (metadataBehavior == null)
{
metadataBehavior = new ServiceMetadataBehavior();
host.Description.Behaviors.Add(metadataBehavior);
}
var debugBehavior = host.Description.Behaviors.OfType<ServiceDebugBehavior>().SingleOrDefault();
if (debugBehavior == null)
{
debugBehavior = new ServiceDebugBehavior();
host.Description.Behaviors.Add(debugBehavior);
}
metadataBehavior.HttpGetEnabled = true;
debugBehavior.IncludeExceptionDetailInFaults = true;
var binding = new WebHttpBinding { MaxBufferPoolSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue };
binding.Security.Mode = WebHttpSecurityMode.None;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
WebHttpBehavior webHttpBehavior = new WebHttpBehavior { HelpEnabled = true };
foreach (var contract in type.GetInterfaces().Where(i => i.GetCustomAttributes(typeof(ServiceContractAttribute), true).Length > 0))
{
var endpoint = host.AddServiceEndpoint(contract, binding, "");
endpoint.Behaviors.Add(webHttpBehavior);
}
host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
return host;
}
#endregion
}

Securing WCF using NTLM CustomBinding and A Custom Principal in asp.net

In securing a WCF service I want to use my active directory custom principal used in the asp.net website hosting the service. All is fine when navigating the website and the Custom Principal is setup using the following code
static void context_AuthenticateRequest(object sender, EventArgs e)
{
CustomIdentity identity;
CustomPrincipal principal = GetPrincipalFromCookie();
if (principal == null)
{
... create principal from active directory
... Store Principal in a cookie
}
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
HOWEVER when I make a WCF call to my service a login dialog appears removing the line HttpContext.Current.User = principal results in the login dialog not appearing but that is no use to me as I need this in order for WebUserSecurityContext to be populated for my application. I am using customBinding with NTLM
<httpTransport authenticationScheme="Ntlm"
maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647"
maxBufferPoolSize="2147483647" proxyAuthenticationScheme="Anonymous"/>
</binding>
</customBinding>
Am I missing a setting in my configuration to prevent this login dialog box or is there a better way? any help would be appreciated
HttpContext.Current is not considered until AspNetCompatibilityMode is enabled. Have you enabled AspNetCompatibilityMode for service?

Impersonation WCF

I have a WCF service, hosted in IIS, which I require to impersonate the annon account.
in my Webconfig
<authentication mode="Windows"/>
<identity impersonate ="true"/>
Testing the following, with vs2008
public void ByRuleId(int ruleId)
{
try
{
string user = WindowsIdentity.GetCurrent().Name;
string name = Thread.CurrentPrincipal.Identity.Name;
........
//get the data as a string.
using (FileStream fs = File.Open(location, FileMode.Open))
using (StreamReader reader = new StreamReader(fs))
{
rawData = reader.ReadToEnd();
}
}
catch.....
}
this works. however if I add impersonation attribute
[OperationBehavior(Impersonation=ImpersonationOption.Required)]
public void ByRuleId(int ruleId)
this does not work with the error message
"Either a required impersonation level was not provided, or the provided impersonation level is invalid."
a little poking around I noticed the first way was authenticated by Kerboros and the second way just failed on authentication type
I am using the WCF client tool, to pass my credentials. this seems to be working.
Check the 'TokenImpersonationLevel' of identity of the current thread; you'll need it to be at least 'Impersonation' to perform operations on the machine that the service is running on.
Typically, if you are using a proxy client, you'll need to set the 'TokenImpersonationLevel' of the client:
http://www.devx.com/codemag/Article/33342/1763/page/4
the main goal of this was to get anon access, even tho MattK answer was a great help.
here is what i did to do so.
on the implementation of the WCF contract I added the
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class TransferFile : ITransferFile
and in the web.config
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled ="true" />
after this i was able to impersonate the anon account

Resources