Securing WCF using NTLM CustomBinding and A Custom Principal in asp.net - 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?

Related

ASP.NET Web Forms Site - integration with multiple ADFS using OWIN KATANA

I'm configuring an old existing web forms site as a multi-tenant environment. One requirement is to be able to integrate with multiple client ADFS.
I have followed this post and have successfully implemented an MVC application supporting multiple ADFS. However I still face an issue,
that is not reproducible with the MVC app. In my web forms site, only the first ADFS provider registered succeeds. The second one always
throws SignatureVerificationFailedException after authenticating and returning back to my site (the exception happens at my side).
This is no matter whether I use app.Map(...) or app.Use(...) in the OWIN startup configuration.
I tried converting my web site to web application, but same result. I guess it is something connected with the way requests are handled in WEB FORMS, which is different than MVC.
Should I handle the middleware mapping in some different way?
What am I missing? Or this is not possible at all?...
Here is my OWIN startup configuration:
app.Properties["Microsoft.Owin.Security.Constants.DefaultSignInAsAuthenticationType"] = Config.ExternalAuthentication.Cookie;
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = Config.ExternalAuthentication.Cookie,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive
});
string wreply = Config.ExternalAuthentication.Wreply;
string wtrealm = Config.ExternalAuthentication.Wtrealm;
List<Company> adfsCompanies = BL.GetCompaniesWithADFS();
app.Map("/Public/Login.aspx", configuration =>
{
foreach (Company company in adfsCompanies)
{
//configure middleware
var middleware = new WsFederationAuthenticationOptions()
{
MetadataAddress = company.ADFSMetadataUrl,
AuthenticationType = company.TenantName,
Caption = company.Name,
Wreply = wreply,
Wtrealm = wtrealm,
BackchannelCertificateValidator = null
};
//add to pipeline
configuration.UseWsFederationAuthentication(middleware);
}
});
Here is my challenge request:
context.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = callbackUrl },
provider);
response.StatusCode = 401;
response.End();
No matter what I do, only the first registered ADFS middleware succeeds, no matter which one. I also tried attaching the middlewares to different pipeline stages with no success.
Thanks in advance for any help!
For multiple wsfed middleware each should set a unique WsFederationAuthenticationOptions.CallbackPath, e.g. "/ws1". You'll also need to include this value in the wreply.
I'm just giving some more details to the solution suggested by #Tratcher, in order to keep the question clean and simple:
1) According to MSDN, CallbackPath if not set is calculated from Wreply;
2) after distinguishing Wreply for each provider it turned out that was not enough, because other problems appeared. Then I found (using my MVC working sample) that furthermore Wtrealm and Wreply should have the same value;
3) Setting different URLs for different providers in ASP.NET Web Forms turned out to not be so easy. Fake URLs did not work. Using URL Rewrite - also. The most straightforward solution is to use different callback page for each provider (e.g. ExternalLoginFirstADFS.aspx, ExternalLoginSecondADFS.aspx, ...). This, although working fine, is not the best, so I decided to configure a Route for each provider at Application_Start event in Global.asax like this:
void Application_Start(object sender, EventArgs e)
{
...
RegisterRoutes(System.Web.Routing.RouteTable.Routes);
}
public static void RegisterRoutes(System.Web.Routing.RouteCollection routes)
{
List<Organization> adfsCompanies = OrgElementEntity.GetCompaniesWithADFS();
foreach(Organization company in adfsCompanies)
{
routes.MapPageRoute("",
String.Format("Public/ExternalLogin{0}.aspx", company.TenantName),
"~/Public/ExternalLogin.aspx");
}
}
Furthermore, it turned out that there is no need too use app.Map(...) at OwinStartup. Just adding each middleware through app.UseWsFederationAuthentication(...) seems to be fine!

Thread.CurrentPrincipal cannot be set to Forms Authentication principal

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.

Prevent FormsAuthenticationModule of intercepting ASP.NET Web API responses

In ASP.NET the FormsAuthenticationModule intercepts any HTTP 401, and returns an HTTP 302 redirection to the login page. This is a pain for AJAX, since you ask for json and get the login page in html, but the status code is HTTP 200.
What is the way of avoid this interception in ASP.NET Web API ?
In ASP.NET MVC4 it is very easy to prevent this interception by ending explicitly the connection:
public class MyMvcAuthFilter:AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest() && !filterContext.IsChildAction)
{
filterContext.Result = new HttpStatusCodeResult(401);
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.SuppressContent = true;
filterContext.HttpContext.Response.End();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
But in ASP.NET Web API I cannot end the connection explicitly, so even when I use this code the FormsAuthenticationModule intercepts the response and sends a redirection to the login page:
public class MyWebApiAuth: AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if(actionContext.Request.Headers.Any(h=>h.Key.Equals("X-Requested-With",StringComparison.OrdinalIgnoreCase)))
{
var xhr = actionContext.Request.Headers.Single(h => h.Key.Equals("X-Requested-With", StringComparison.OrdinalIgnoreCase)).Value.First();
if (xhr.Equals("XMLHttpRequest", StringComparison.OrdinalIgnoreCase))
{
// this does not work either
//throw new HttpResponseException(HttpStatusCode.Unauthorized);
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
return;
}
}
base.HandleUnauthorizedRequest(actionContext);
}
}
What is the way of avoiding this behaviour in ASP.NET Web API? I have been taking a look, and I could not find a way of do it.
Regards.
PS: I cannot believe that this is 2012 and this issue is still on.
In case someone's interested in dealing with the same issue in ASP.NET MVC app using the Authorize attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class Authorize2Attribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new HttpStatusCodeResult((int) HttpStatusCode.Forbidden);
}
else
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
This way browser properly distinguishes between Forbidden and Unauthorized requests..
The release notes for MVC 4 RC imply this has been resolved since the Beta - which are you using?
http://www.asp.net/whitepapers/mvc4-release-notes
Unauthorized requests handled by ASP.NET Web API return 401 Unauthroized: Unauthorized requests handled by ASP.NET Web API now return a standard 401 Unauthorized response instead of redirecting the user agent to a login form so that the response can be handled by an Ajax client.
Looking into the source code for MVC there appears to be an functionality added via SuppressFormsAuthRedirectModule.cs
http://aspnetwebstack.codeplex.com/SourceControl/network/forks/BradWilson/AspNetWebStack/changeset/changes/ae1164a2e339#src%2fSystem.Web.Http.WebHost%2fHttpControllerHandler.cs.
internal static bool GetEnabled(NameValueCollection appSettings)
{
// anything but "false" will return true, which is the default behavior
So it looks this this is enabled by default and RC should fix your issue without any heroics... as a side point it looks like you can disable this new module using AppSettings http://d.hatena.ne.jp/shiba-yan/20120430/1335787815:
<appSettings>
<Add Key = "webapi:EnableSuppressRedirect" value = "false" />
</appSettings>
Edit (example and clarification)
I have now created an example for this approach on GitHub. The new redirection suppression requires that you use the two correct "Authorise" attribute's; MVC Web [System.Web.Mvc.Authorize] and Web API [System.Web.Http.Authorize] in the controllers AND/OR in the global filters Link.
This example does however draw out a limitation of the approach. It appears that the "authorisation" nodes in the web.config will always take priority over MVC routes e.g. config like this will override your rules and still redirect to login:
<system.web>
<authentication mode="Forms">
</authentication>
<authorization>
<deny users="?"/> //will deny anonymous users to all routes including WebApi
</authorization>
</system.web>
Sadly opening this up for some url routes using the Location element doesn't appear to work and the WebApi calls will continue to be intercepted and redirected to login.
Solutions
For MVC applications I am simply suggest removing the config from Web.Config and sticking with Global filters and Attributes in the code.
If you must use the authorisation nodes in Web.Config for MVC or have a Hybrid ASP.NET and WebApi application then #PilotBob - in the comments below - has found that sub folders and multiple Web.Config's can be used to have your cake and eat it.
I was able to get around the deny anonymous setting in web.config by setting the following property:
Request.RequestContext.HttpContext.SkipAuthorization = true;
I do this after some checks against the Request object in the Application_BeginRequest method in Global.asax.cs, like the RawURL property and other header information to make sure the request is accessing an area that I want to allow anonymous access to. I still perform authentication/authorization once the API action is called.

Impersonating ASP.NET claims identity to windows identity

I have an ASP.NET application which uses claims bases authentication against ADFS. I also map it to a WindowsClaimsIdentity by using the Claims to Windows Identity Service. That works fine.
But now I need to impersonate the current request/thread so I can access a service which is not claims aware. How should I do that?
Should I acquired a WindowsImpersonationContext in the Application_PostAuthenticate event and save that in the HttpContext.Items and then in the Application_EndRequest call the Undo method?
Or are there other preferred ways to do this?
Update: As I didn't get any hints on what the preferred way to impersonate I tried my own suggestion. I created this code in the global.asax.cs:
private static readonly string WICKey = typeof(System.Security.Principal.WindowsImpersonationContext).AssemblyQualifiedName;
protected void Application_PostAuthenticateRequest()
{
var wid = User.Identity as System.Security.Principal.WindowsIdentity;
if (wid != null)
{
HttpContext.Current.Trace.Write("PostAuthenticateRequest PreImpersonate: " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
HttpContext.Current.Items[WICKey] = wid.Impersonate();
HttpContext.Current.Trace.Write("PostAuthenticateRequest PostImpersonate: " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
}
}
protected void Application_EndRequest()
{
var wic = HttpContext.Current.Items[WICKey] as System.Security.Principal.WindowsImpersonationContext;
if (wic != null)
{
HttpContext.Current.Trace.Write("EndRequest PreUndoImpersonate: " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
wic.Undo();
HttpContext.Current.Trace.Write("EndRequest PostUndoImpersonate: " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
}
}
When I look to the trace log I see this
PostAuthenticateRequest PreImpersonate: NT AUTHORITY\NETWORK SERVICE
PostAuthenticateRequest PostImpersonate: MyDomain\CorrectUser
Home: NT AUTHORITY\NETWORK SERVICE
EndRequest PreUndoImpersonate: NT AUTHORITY\NETWORK SERVICE
EndRequest PostUndoImpersonate: NT AUTHORITY\NETWORK SERVICE
So in the second line you can see the thread is impersonated correctly. But in the next lines you see that the impersonation is lost. (the third line originates from a controller).
When I use the following code to impersonate locally it works fine:
var wid = User.Identity as System.Security.Principal.WindowsIdentity;
if (wid != null)
{
using (var ctx = wid.Impersonate())
{
//Do something
}
}
But I want to impersonate the whole request lifetime.
How should I do that?
You said the backend service is not claims aware. Can you elaborate on this? Do you mean that the compiled code is not claims aware but you have the ability modify the web.config file? If so then you can try to configure the backend service to use the WIF pipeline for authN by wedging in the WSFederationAuthenticationModule, SessionAuthenticationModule and a custom ClaimsAuthorizationManager if you need to also do authZ. You can then use WIF's ActAs or OnBehalfOf features when your ASP.NET application calls the backend service.
Sorry for digging up this old thread, but for your code to work make sure the Managed Pipeline Mode of the Application Pool running your application is set to Classic.

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