Asp.net Web api2 impersonate on webClient call - c#

we have a web api what has is how user account let's say ApplicationPoolUser
that used have access to the databases used by the api, etc which work fine.
but i'm trying to send a http get method on files on a remote server(sharepoint 2007) using webClient
here's what im using :
WindowsImpersonationContext impersonationContext = null;
Uri uri = new Uri(Path.Combine(this.Document.path , Document.fileNameOriginal));
Stream stream = null;
// WindowsIdentity.GetCurrent().Name return 'ApplicationPoolUser'
try
{
WindowsIdentity wi = System.Web.HttpContext.Current.Request.LogonUserIdentity;
impersonationContext = WindowsIdentity.Impersonate(wi.Token);
// WindowsIdentity.GetCurrent().Name return 'CurrentRequestingUser'
WebClient client = new WebClient() {
UseDefaultCredentials = true,
CachePolicy = new System.Net.Cache.RequestCachePolicy(RequestCacheLevel.BypassCache)
};
stream = client.OpenRead(uri);
// OpenRead Authentified on sharepoint server has ApplicationPoolUser
}
catch(WebException ex)
{
HttpWebResponse webResp = (HttpWebResponse)ex.Response;
if(webResp.StatusCode == HttpStatusCode.NotFound)
throw new GeneralException(Common.Enums.ExceptionMessage.NotFound, webResp.StatusDescription);
else
{
throw ex;
}
}
is there a way to force the authentification on behalf of the user without turning asp.net Identity ON ? in the web.config / IIS site.
I dont want the whole code to execute has the impersonated user request just this small part ...
I did try to use httpClient instead by i've found that since httpclient start in a new thread, it will always use the application pool identity.
can i create the Negotiate Call myself and add it to the request ?
thank you.
EDIT :
i have tried Removing all AuthenticationManager except Kerberos, and the request still use NTLM for authentication, what am i doing wrong ?

There are multiple factors, which can make an impersonation or to be precise a delegation of the user credentials impossible.
1) if you are using asynchronous methods (directly or not) you might experience a problem with flowing the identity. You can check, if that might be an problem with the following call:
System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed();
This should return false - if not you can use this as a reference: Calling an async WCF Service while being impersonated
2) You have to enable constrained delegation for the executing account. You have a so called Kerberos double hop scenario. You have to allow the Sharepoint user to act as another user or else impersonate() will not succeed as expected.

Related

Basic authentication on a remote server

I need some help with ASMX web-services.
Let's suppose I have a ServerService which provides some data. Let's suppose it has a method GetRandomInteger which returns a random integer (obviously). It implements a custom basic authentication using IHttpModule.
public class BasicAuthHttpModule : IHttpModule
{
private UserRepository _userRepository;
public void Dispose()
{
}
public void Init(HttpApplication application)
{
_userRepository = new UserRepository();
application.AuthenticateRequest += OnAuthenticateRequest;
application.EndRequest += OnEndRequest;
}
public void OnAuthenticateRequest(object source, EventArgs e)
{
var app = (HttpApplication)source;
string authHeader = app.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader))
{
// Here I successfully get credentials from header
if (_userRepository.ValidateUser(username, password)) return;
// Return 401 and CompleteRequest
}
else
{
// Return 401 and End
}
}
public void OnEndRequest(object source, EventArgs eventArgs)
{
if (HttpContext.Current.Response.StatusCode == 401)
{
// Return 401 and require new authorization
}
}
Fortunately, it works. Now I can successfully open Service.asmx file, get basic authentication window and get access to it's GetRandomInteger method after successful authentication.
Now I have an ASP.NET MVC 4 application called ClientService. It must provide user interface with convenient and appropriate access to methods of ServerService. Now it has default controllers like Account and Home, default views etc.
I need this ClientService to authenticate on a ServerService. I mean there will be a Home/Index page with button "Login". I enter login and password there and ClientService tries to authenticate at ServerService. It returns error on fail or authenticates on success providing access to some Home/RandomInt page which will show the integer requested from ServerService. What is the best and the easiest way to do this?
How to implement registration on a ServerService? There is no AllowAnonymous attribute or something at ASMX, so I can't register user because he doesn't have access to any of methods due to 401 error.
Thank you in advance.
P.S. No. I can't use WCF or something else. I need to implement an ASMX web-service.
Update 1: OK, I have learned something new from here
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
There is an old-style thing like "Web reference" and it's not an "Service reference". I have added this Web reference to my project and now I can call some methods from this ASMX page in this way:
try
{
ServerService svc = new ServerService();
svc.Credentials = new NetworkCredential("user", "password");
int a = svc.GetRandomInteger();
} catch (WebException e) {
// Auth failed
}
However, I don't understand how to link it with ASP.NET MVC ClientService authentication. So, both questions are still open. Hopefully, I will understand it or you will help me.
Here is a documentation for adding a Web reference to an ASMX Service.
http://www.aspsnippets.com/Articles/How-to-add-reference-of-Web-Service-ASMX-in-ASPNet-using-Visual-Studio.aspx
Using this information I can easily make requests to a web service.
The only thing I left to do on the moment of question update is to create a custom authentication.
When user logins, the client sends a request to a service. In case of successful basic authentication, it creates proper FormsAuthentication cookie ticket for a user. User logs in.
On each request to a service, the client extracts login from FormsAuthentication cookie and his password from server cache and uses them to authenticate on a service. In case of basic auth failure (it can only occur if user's password has been changed on the service side) the cookie is cleared and session is aborted.
Registration is implemented using another one ASMX service which is not using basic auth but is anonymous (because registration is supposed to be anonymous method).
That's it. Finally, I have found a proper solution :)

Delegation not working

I'm trying to get delegation working to call a webservice using WebClient (or HttpClient for that matter). I have a MVC4 web application with a controller and a Web Api controller. I have the application set up on a remote server on the intranet, and I'm running the same application on my local computer, using IIS on both computers, with Basic Authentication.
The Web Api controller is simple, this is the code for the service:
[Authorize(Users="domain\\username")]
[HttpPost]
[HttpGet]
public string GetSimulationTest()
{
return "WS successfully called";
}
This WS works perfectly fine from my browser or fiddler, both locally and remote.
In my home controller, I have the following method:
public ActionResult TestOwnWS()
{
ContentResult res = null;
WindowsIdentity wi = (WindowsIdentity)User.Identity;
WindowsImpersonationContext ctx = null;
try
{
ctx = wi.Impersonate();
WebClient wc = new WebClient();
wc.UseDefaultCredentials = true;
wc.Headers.Add("Content-Type", "application/xml");
res = this.Content(wc.DownloadString("linktoWS"), "application/xml");
}
catch (Exception e)
{
Response.Write(e.Message);
Response.End();
}
finally
{
ctx.Undo();
}
return res;
}
Here's the problem: I get 401 Unauthorized on the wc.DownloadString() call. It doesn't even work if I use the local webservice rather than the remote one. If I however set up wc.Credentials manually using wc.Credentials = new NetworkCredentials(user,pass,domain); it works.
I used Windows Authentication before, but it still refused to work, so I read up on delegation and apparently, it should work fine if the accounts are identical on both computer (which they are) with basic authentication while Windows Authentication is more finicky.
Why does it refuse to use the default credentials, am I still getting delegation wrong? I've read msdn articles on it and I can't find what I'm doing incorrectly.
Authorize is a filter that allows certain users to access the action method.
WindowsIdentity wi = (WindowsIdentity)User.Identity;
The above code still gets the identity of the logged-in user. Impersonation is not the right way to do this. The code for impersonation requires you to login with the impersonated user, and get the Windows identity with a handle.
MSDN Link
The best way is to use the NetworkCredentials as you have mentioned.

Impersonation, Active Directory, and "user does not have authority to xxxx" issues

I have 2 ASP.NET MVC 3 applications. I am using impersonation via the web.config to allow me to query Active Directory to get details on the user. The application uses Windows authentication and does not allow anonymous users. One application is the primary application where the user performs their tasks. The other allows the user to set up other user's to look like them in application one.
The test user's are getting the following error:
SQL1092N "<DOMAIN ID>" does not have the authority to perform the requested command.
This happens after I send a web request from my primary application to the secondary one. To get that working I had to make the request impersonate the actual user and not the identity the application uses for impersonation. This is actually an SO question I posted and had answered. That's here: How do I call an MVC Action via a WebRequest and validate the request through Active Directory?
At the end of that code, I call:
impersonationContext.Undo();
It is after this web request takes place, that the primary application tries accessing the database and now it seems that the above call has undone the impersonation of the application, so the user's attempt to do anything that opens a database connection fails. At least, that's my working theory after a day of head bashing.
My question is, how can I get the impersonation of the application to revert back to the user in the web.config? Or, when making my web request, is there a way to ensure the impersonation context only applies to that request?
The whole point of all of this is that the second application has its own sql server database. The primary application uses DB2. I would like to write the database access code once, but use it in both applications. Currently that's what I've done, but my method of relying on the web request to get the data may not be the best approach.
I'm open to any thoughts, comments, suggestions, and/or criticism. How should I go about handling this?
Okay...my theory that the IPrincipal context was changed when making the web request proved accurate, which made this fix extremely easy. Best part is, I can keep using the api I built to make this request without duplicating the Sql Server Entity Framework parts.
I have the following call to my api library:
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
This code is being called by an authorization filter attribute. The method prototype looks like
public void OnAuthorization(AuthorizationContext filterContext)
Internally, the call makes the GetProxies method following call:
public static StreamReader GetWebRequestStream(
string url,
string contentType,
bool useDefaultCredentials,
IPrincipal user)
{
var impersonationContext = ((WindowsIdentity)user.Identity).Impersonate();
var request = WebRequest.Create(url);
try
{
request.ContentType = contentType;
//request.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
//request.UseDefaultCredentials = useDefaultCredentials;
//IWebProxy p = new WebProxy();
//request.Proxy = p.
request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
request.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
var response = (HttpWebResponse)request.GetResponse();
return new StreamReader(response.GetResponseStream());
}
catch (Exception e)
{
impersonationContext.Undo();
throw e;
}
finally
{
impersonationContext.Undo();
}
}
When the calling method returns, the identity of user is no longer that of the one set for the application to
impersonate. The fix is pretty simple:
//Track current identity before proxy call
IPrincipal user = context.User;
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
//Undo any impersonating done in the GetProxies call
context.User = user;
2 lines of code resolved 12 hours of head ache. It could have been worse. Anyhow. Thanks for being a sounding board. I tried
having this conversion with the duck, but the duck got confused.

Creating Delegation token - can't create a SecurityTokenService

I'm trying to build a system working with ADFS and claims. At the moment, this is just a "toy" implementation.
I've built a very simple MVC web application, set it up using the "Identity and Access..." wizard in Visual Studio to talk to an ADFS 2.0 server, and deployed it to an IIS server. All works fine, and I can examine and list the received claims.
The next step is to build a Web API based REST service (representing back-end services that the MVC application is going to depend on), so I want to pass the credentials across to that back-end server so that it can make suitable authorization decisions.
So the first step is for me to create the delegation token (and I'll then, hopefully, work out what to do with it in terms of the HttpClient class to make the rest call). I've got this:
//We need to take the bootstrap token and create an appropriate ActAs token
var rst = new RequestSecurityToken
{
AppliesTo = new EndpointReference("https://other-iis.example.com/Rest"),
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
ActAs = new SecurityTokenElement(((BootstrapContext)((ClaimsIdentity)User.Identity).BootstrapContext).SecurityToken)
};
var sts = new SecurityTokenService(); //This line isn't valid
var resp = sts.Issue(System.Threading.Thread.CurrentPrincipal as ClaimsPrincipal, rst);
But, the issue is that SecurityTokenService is abstract. I can't find any types derived from this class in either System.IdentityModel nor System.IdentityModel.Services, and the above doesn't include any reference to the ADFS server which I'll obviously need to provide at some point.
Of course, I may be going down completely the wrong route also, or am just hitting a minor stumbling block and not seeing a much larger one looming in the distance, so any advice on that would be appreciated also.
I've looked at, for example, Identity Delegation Scenario, but that uses CreateChannelActingAs, which I don't think is going to work when I'm talking to a rest service (or will it?), and also doesn't seem to apply to .NET 4.5.
I am requesting tokens from an ADFS 2.0 for caching and looking at the DisplayToken. Maybe this can help you get started.
Here is what I can up with:
public SecurityToken GetToken(out RequestSecurityTokenResponse rstr)
{
Console.WriteLine("Connecting to STS...");
WSTrustChannelFactory factory = null;
try
{
if (_useCredentials)
{
// use a UserName Trust Binding for username authentication
factory =
new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
"https://<adfs>/adfs/services/trust/13/UsernameMixed");
factory.TrustVersion = TrustVersion.WSTrust13;
// Username and Password here...
factory.Credentials.UserName.UserName = "username";
factory.Credentials.UserName.Password = "password";
}
else
{
// Windows authentication over transport security
factory = new WSTrustChannelFactory(
new WindowsWSTrustBinding(SecurityMode.Transport),
"https://<adfs>/adfs/services/trust/13/windowstransport") { TrustVersion = TrustVersion.WSTrust13 };
}
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = SvcEndpoint,
KeyType = KeyTypes.Symmetric,
RequestDisplayToken = true
};
Console.WriteLine("Creating channel for STS...");
IWSTrustChannelContract channel = factory.CreateChannel();
Console.WriteLine("Requesting token from " + StsEndpoint.Uri);
SecurityToken token = channel.Issue(rst, out rstr);
Console.WriteLine("Received token from " + StsEndpoint.Uri);
return token;
}
finally
{
if (factory != null)
{
try
{
factory.Close();
}
catch (CommunicationObjectFaultedException)
{
factory.Abort();
}
}
}
}
You might have to acivate the UsernameMixed Endpoint in your ADFS 2.0 if you want to use it and don't forget to restart the service afterwards!
From msdn
To create an STS you must derive from the SecurityTokenService class. In your custom class you must, at a minimum, override the GetScope and GetOutputClaimsIdentity methods.
Not sure how much this will help you, but You're not supposed to create a SecurityTokenService. You are not creating a new token here, and your aplication is not supposed to act as the STS - this is what the AD FS is for.
Your application should only delegate the token received from the AD FS to the service (the concept is described in the link from msdn you provided in your question)
Im guessing theres a good chance the web api will suppor this as well, as its built upon wcf, and from the http point of view - theres no reason it wont support a ws-federation/saml 2 tokens.
EDIT:
This video (starting at 35:00+-) shows a way, i think, to implement what youre looking for, with ws-federation saml token. im guessing its also possible with a saml2 token

Proxy Authentication Required (Forefront TMG requires authorization to fulfill the request. Access to the Web Proxy filter is denied.)

When I was trying to post messages to Twitter, the above error coming. How to get rid of that error?
The stacktrace is the following:
Exception = {"The remote server returned an error: (407) Proxy Authentication Required."} ExceptionStatus = ProtocolError
Code:
private string GetOAuthUrl()
{
IFluentTwitter twitter;
//Override the callback url if one was entered
if (CallbackUrl != null && CallbackUrl.Trim().Length > 0)
{
twitter = FluentTwitter.CreateRequest().Configuration.UseHttps().Authentication.GetRequestToken(ConsumerKey, ConsumerSecret, CallbackUrl);
}
else
{
twitter = FluentTwitter.CreateRequest().Configuration.UseHttps().Authentication.GetRequestToken(ConsumerKey, ConsumerSecret);
}
var response = twitter.Request();
UnauthorizedToken UnauthorizedToken = response.AsToken();
string AuthorizationUrl = FluentTwitter.CreateRequest().Authentication.GetAuthorizationUrl(UnauthorizedToken.Token);
return AuthorizationUrl;
}
If fluent twitter is using WebRequests under the covers, then you need to specify credentials for the proxy using code like this:
System.Net.WebRequest.DefaultWebProxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
This will tell all web requests to use the credentials of the user running the application to authenticate with the proxy.
To make this work, you will need to configure the application to run under a service account which has been granted access to the proxy server. You can then tie down this service account so that it has as few permissions as possible to run the service.
If your application needs to run under an account which doesn't have rights to use the proxy server, you can specify the credentials explicitly as follows:
System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("username", "password", "domain");
System.Net.WebRequest.DefaultProxy.Credentials = credentials;
The down side to this is that you have to store those credentials somewhere, and that they could be captured by an attacker if they managed to compromise your application. In some environments, this is not acceptable from a security standpoint.

Categories