Hi guys i'm having a hard time to figure out why the Impersonation in WindowsIdentity does not work.
I will present the idea and also the code in the following.
So the idea is that i need to access within my application another application API.
public async Task<UserHrtbProfileDTO> HasHrtbAccessAsync(int userId, string systemUser)
{
WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
using (identity.Impersonate())
{
using (HttpClientHandler handler = new HttpClientHandler
{
Credentials = CredentialCache.DefaultNetworkCredentials,
UseDefaultCredentials = true
})
{
using (HttpClient client = new HttpClient(handler, true))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string requestURI = string.Format(ConfigurationManager.AppSettings["HRTB:Profile_Url"], userId);
HttpResponseMessage response = await client.GetAsync(requestURI);
return response.RequestMessage.RequestUri.OriginalString.Contains(requestURI)
? new UserHrtbProfileDTO
{
HrtbProfileUrl = string.Format(ConfigurationManager.AppSettings["HRTB:Profile_Url"], userId),
ResponseURI = response.RequestMessage.RequestUri.AbsoluteUri,
}
: new UserHrtbProfileDTO
{
ResponseURI = response.RequestMessage.RequestUri.AbsoluteUri,
RequestURI = requestURI,
HasHrtbAccess = false,
IdentityUserName = identity.Name + "\n" + identity.IsAuthenticated + "\n" + identity.AuthenticationType + "\n" + identity.ImpersonationLevel + "\n"
};
}
}
}
}
If i'm running on local IIS the behavior is OK, i have access where i need to have and the access is being denied where i don't have it. The API response for which the request is made returns the response that i expect.
But when i deploy and run from a server the response is different thus the behavior is not as it should.
I'm guessing that the request is not emitted from the perspective of the user that has made it.
Any idea ,suggestions, or a different approach in which i can solve the issue.
Thank you all.
For me it worked enabling impersonation from web.config file and set the IIS application pool to classic managed pipeline.
If you have any other knowledge of other solutions, please share.
Thank you
Related
I am trying to setup IdentityServer3 for my project.
When I run IdentityServer3 on local development machine it works all fine, but when I host it on the shared server I get a 401 error. I am trying to access token using endpoint connect\token. Here is the configuration for identityserver3
IdentityServerOptions identityServerOptions = new IdentityServerOptions
{
SiteName = "Ripple IdentityServer",
SigningCertificate = LoadCertificate(),
AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions
{
EnablePostSignOutAutoRedirect = true,
},
LoggingOptions = new LoggingOptions
{
EnableWebApiDiagnostics = true,
WebApiDiagnosticsIsVerbose = true,
EnableHttpLogging = true,
EnableKatanaLogging = true
},
Factory = factory,
};
The strange thing is I am not getting any logs. I know the logs are working because when I access the connect/authorize endpoint, I can see log information. Here is my client registration
client = new Client
{
ClientId = app.Id,
ClientName = app.Name,
Flow = Flows.ResourceOwner,
AllowedScopes = app.AllowedScopes.Split(';').ToList(),
AllowedCorsOrigins = new List<string> { "*" }
};
if (app.Secret != null && app.Secret != "")
{
client.ClientSecrets = new System.Collections.Generic.List<Secret>();
app.Secret = app.Secret.Replace("{", "").Replace("}", "");
string[] secrets = app.Secret.Split(',');
foreach (var s in secrets)
{
client.ClientSecrets.Add(new Secret(s.Sha256()));
}
}
Here is the client code to get access token
var data = new StringContent(string.Format("grant_type=password&username={0}&password={1}&Domain={2}&scope={3}",
HttpUtility.UrlEncode(username),
HttpUtility.UrlEncode(password),
HttpUtility.UrlEncode(domainId),
HttpUtility.UrlEncode(requiredScope)), Encoding.UTF8, "application/x-www-form-urlencoded");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", applicationId, appSecretKey))));
HttpResponseMessage response = client.PostAsync("connect/token", data).Result;
Without logs, I am totally lost. Where should I look for more information to debug?
Found solution. Shared hosting like godaddy did not support Basic authentication. So request to access token was getting rejected on server level. That was the reason why no log file was not are getting generated.
To work around this problem, I have to implement my own version on ISecretParser. In this implementation i parsed of my own authentication header
e.g. Authentication MyAuth ClientID:ClientSecret
Then register this parser with IdentityServerServiceFactory and it worked like charm.
I hope this solution will help others who are trying to host IdentiyServer3 on shared servers.
I'm finding very little documentation online for implementing the PayPal REST API in C#.
I have gotten past the first step of getting an access token, but I keep seeing conflicting methods for sending API calls and nothing I have tried works.
Here's my current code:
private async void cmdRefund_Click(object sender, EventArgs e)
{
//var apiContext = Configuration.GetAPIContext();
string AccessToken;
string Nonce;
string AppID;
string TokenType;
try
{
// ClientId of your Paypal app API
//This is the live ID
string APIClientId = "AZxxxx-8";
//this is the live secret Key
string APISecret = "Exxxx39";
using (var client = new HttpClient())
{
var byteArray = Encoding.UTF8.GetBytes(APIClientId + ":" + APISecret);
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
//this is the live url
var url = new Uri("https://api.paypal.com/v1/oauth2/token", UriKind.Absolute);
client.DefaultRequestHeaders.IfModifiedSince = DateTime.UtcNow;
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var content = new FormUrlEncodedContent(requestParams);
var webresponse = await client.PostAsync(url, content);
var resp = await webresponse.Content.ReadAsStringAsync();
MessageBox.Show(resp);
if (resp.IndexOf("access_token") == -1)
{
MessageBox.Show("PayPal Authorization Failed.");
return;
}
AccessToken = resp.Substring(resp.IndexOf("access_token") + 15);
AccessToken = AccessToken.Substring(0, AccessToken.IndexOf("\""));
Nonce = resp.Substring(resp.IndexOf("nonce") + 8);
Nonce = Nonce.Substring(0, Nonce.IndexOf("\""));
AppID = resp.Substring(resp.IndexOf("app_id") + 9);
AppID = AppID.Substring(0, AppID.IndexOf("\""));
TokenType = resp.Substring(resp.IndexOf("token_type") + 13);
TokenType = TokenType.Substring(0, TokenType.IndexOf("\""));
MessageBox.Show("Access Token: \r\n" + AccessToken + "\r\nNonce:\r\n" + Nonce + "\r\nAppID:\r\n" + AppID + "\r\nToken Type:\r\n" + TokenType);
// response will deserialized using Jsonconver
//return JsonConvert.DeserializeObject<PayPalGetTokenResponse>(resp);
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
//Authorization has been achieved here - now I want to process a refund
var apiContext = new APIContext(AccessToken);
Amount refundAmount = new Amount();
refundAmount.total = "0.01";
refundAmount.currency = "USD";
Refund refund = new Refund();
refund.amount = refundAmount;
string saleId = "9XY489008U7836633";
//*************THIS NEXT LINE CAUSES AN ERROR
Refund refundforreal = Sale.Refund(apiContext, saleId, refund);
//*************
string refundId = refund.id;
}
The last line causes the Error: "PayPal.IdentityException: 'The remote server returned an error: (401) Unauthorized.'"
As far as I can tell, my access token is completely valid, but this does not work. I should note that I the transactions I'm trying to get information on and refund are NOT placed via the REST API, but are simply placed through the regular PayPal interface integrated on our website. I don't know if that causes a problem or not, but that is what I need to do.
I am using a Windows Forms App written in C# in Visual Studios 2017 because I'm replacing an old VB6 program that required that the user log into a PayPal session in a browser in the program and need to replace that program with something that is both usable and familiar for our employees, AND is more forward thinking by using the REST API instead of the old method of filling in fields in a WebBrowser control.
***********EDIT************ - I added this as a follow up:
I took #shamanthGowdraShankaramurthy's advice and used Postman and managed to do what I wanted, so thank you - that did help me to know that at least what I want to do is possible.
I still don't know how to do the POST in C#. I think perhaps I'll stay away from the built in "Refund" object in the SDK and instead try to POST some other way.
The url I'm using is in Postman is: https://api.paypal.com/v1/payments/sale/9XY489008U7836633/refund
I sent this as the body to do a $0.01 refund on my test transaction:
{
"amount": {
"total": "0.01",
"currency": "USD"
},
"invoice_number": "INV-1234567"
}'
I added a Bearer Token authorization to the POST with my Access Token that I had from my working code.
Finally, in Postman, I changed the body from "Text" to "JSON (application/json).
How do I incorporate all these elements (the URL, my bearer token, the body, and the information that the body is json) into a POST in a C# winforms application?
In case anyone else is looking for this, the answer turned out to be much simpler than I figured. I think I was messing myself up by not using the AccessTokens that are easily obtained by the PayPal SDK.
First, make sure that you have the App.Config file set up properly:
<!-- PayPal SDK settings -->
<paypal>
<settings>
//specify sandbox or live
<add name="mode" value="sandbox" />
<add name="clientId" value="insert_clientid_key_here" />
<add name="clientSecret" value="Insert_client_secret_key_here" />
</settings>
</paypal>
Then this is all the code I needed to make the refunds work:
private void cmdRefund_Click(object sender, EventArgs e)
{
var config = ConfigManager.Instance.GetProperties();
var accessToken = new OAuthTokenCredential(config).GetAccessToken();
var apiContext = new APIContext(accessToken);
Amount refundAmount = new Amount();
refundAmount.total = "0.01";
refundAmount.currency = "USD";
RefundRequest refund = new RefundRefundRequest();
refund.amount = refundAmount;
string saleId = "9XY489008U7836633";
Refund refundforreal = Sale.Refund(apiContext, saleId, refund);
MessageBox.Show("Refund status:" + refundforreal.state + "\r\n" + "Txn #:" + refundforreal.id);
}
Tonight i come search some help about how to call a web api hosted in IIS.
Everything work well in local from visual studio to iis express. But strangely, after publish on my IIS server. I always get 401 unauthorized :'(
Here is the code i use and the settings from my IIS server. I will be very grateful if somebody can help me.
Thank you
**
The controller and the function i try to call (with basic authentication attribute)
**
[HttpGet]
[ActionName("Get_UserID")]
[IdentityBasicAuthentication]
[Authorize]
public HttpResponseMessage Get_UserID(string userName)
{
HttpResponseMessage res = new HttpResponseMessage(HttpStatusCode.Created);
try
{
var user = Membership.GetUser(userName, false);
if (user != null)
{
res = Request.CreateResponse(HttpStatusCode.OK, (Guid)user.ProviderUserKey);
}
else
{
res = Request.CreateResponse(HttpStatusCode.ExpectationFailed);
res.Content = new StringContent("Error");
res.ReasonPhrase = "UserName not find in the database";
}
}
catch (Exception exc)
{
//Set the response message as an exception
res = Request.CreateResponse(HttpStatusCode.InternalServerError);
res.Content = new StringContent("Exception");
res.ReasonPhrase = exc.Message;
}
return res;
}
**
Client side - How i call the web api and build my httpClient
**
public static async Task<HttpResponseMessage> RequestStart(string requestUrl, string webApiUrlBase = Globals.WebApi_Url, bool IsAuthenticateMemberRequest = false)
{
if (webApiUrlBase == null)
{
webApiUrlBase = Globals.WebApi_Url;
}
var response = new HttpResponseMessage(HttpStatusCode.Created);
using (var client = new HttpClient())
{
if (IsAuthenticateMemberRequest)
{
string strToEncode = ApplicationData.Current.LocalSettings.Values["userName"].ToString() + ":" + ApplicationData.Current.LocalSettings.Values["password"].ToString();
var authenticationBytes = Encoding.ASCII.GetBytes(strToEncode);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(authenticationBytes));
}
client.BaseAddress = new Uri(Globals.WebApi_Url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
response = await client.GetAsync(requestUrl);
}
return response;
}
**
IIS Configuration (appPool => NetworkServices - integrate)
**
**
Fiddler Debug
**
Finally after search many times , many houres by myself. I find the solution. We should never enable Basic Authentication....
I know it's weird ^^ But if you want to use your custom basic authentication. Just disabled the Basic Authentication on IIS and everything goes well.
I am developing a Windows Store App which requires user to authenticate using Yammer credentials. I have written the following code to achieve the functionality.
var client_id = <<My Client ID >>;
var client_secret = <<My Client Secret>>;
string redirectURI = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();
string loginURI = "https://www.yammer.com/dialog/oauth?client_id=" + client_id + "&redirect_uri=" + redirectURI;
var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(loginURI), new Uri(redirectURI));
if (result.ResponseStatus == WebAuthenticationStatus.Success)
{
var response = result.ResponseData;
string[] keyValPairs = response.Split('=');
HttpClient client = new HttpClient();
string url = "https://www.yammer.com/oauth2/access_token.json?client_id=" + client_id + "&client_secret=" + client_secret + "&code=" + keyValPairs[1];
HttpResponseMessage JSONresponse = await client.GetAsync(url);
string content = await JSONresponse.Content.ReadAsStringAsync();
}
When I ran the app for the first time it asked me for my credentials and I was able to login.
Now when I run my app , it takes my previous entered credentials and logs in automatically. I am sure that I did not check "Keep Me signed In" option.
I want the app to ask for credentials every time I run it.
Please help and thanks in advance.
I had a similar problem with an app using the Pocket OAUTH 2.0 API. I am using the WebAuthenticationBroker http://msdn.microsoft.com/en-us/library/windows/apps/windows.security.authentication.web.webauthenticationbroker.aspx and its AuthenticateAsync method but I think the problem is the same.
I had used the AuthenticateAsync(options, url) method which ended up -once credentials are saved - I was never able to use a different login so basically could not change accounts.
Tried AuthenticateAsync(options, url, redirectURI) method and same problem.
But then I realized that redirectURI is set to GetCurrentApplicationCallbackUri as in your example this seems to trigger SSO.
Changing the redirectURI to a custom one (e.g. your own domain) seems to disable SSO and credentials have to be added every time as wanted.
This is easy with the AuthenticateAsync method as it uses a WebAuthenticationBroker, not sure how to adapt this with the HttpClient you use above but I maybe you could use
WebAuthenticationBroker?
But my code example (Javascript but same API):
var authweb = Windows.Security.Authentication.Web
var OAUTH_URI = 'https://$WHATEVERSERVICE'
var OAUTH_CODE = '$GOT BEFORE'
var endURI = 'http://$YOURDOMAIN.com' // this is the important part
var authorizeUri = new Windows.Foundation.Uri(OAUTH_URI
+ '?request_token=' + encodeURIComponent(OAUTH_CODE) // POCKET SPECIFIC PARAM
+ '&redirect_uri=' + encodeURIComponent(endURI)
);
authweb.WebAuthenticationBroker.authenticateAsync(
authweb.WebAuthenticationOptions.none,
authorizeUri,
Windows.Foundation.Uri(endURI)
).done(...
This way I get a clean (no prefilled or even submitted) login form every time.
Hope this helps?
I have a web application (hosted in IIS) that talks to a Windows service. The Windows service is using the ASP.Net MVC Web API (self-hosted), and so can be communicated with over http using JSON. The web application is configured to do impersonation, the idea being that the user who makes the request to the web application should be the user that the web application uses to make the request to the service. The structure looks like this:
(The user highlighted in red is the user being referred to in the examples below.)
The web application makes requests to the Windows service using an HttpClient:
var httpClient = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
httpClient.GetStringAsync("http://localhost/some/endpoint/");
This makes the request to the Windows service, but does not pass the credentials over correctly (the service reports the user as IIS APPPOOL\ASP.NET 4.0). This is not what I want to happen.
If I change the above code to use a WebClient instead, the credentials of the user are passed correctly:
WebClient c = new WebClient
{
UseDefaultCredentials = true
};
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));
With the above code, the service reports the user as the user who made the request to the web application.
What am I doing wrong with the HttpClient implementation that is causing it to not pass the credentials correctly (or is it a bug with the HttpClient)?
The reason I want to use the HttpClient is that it has an async API that works well with Tasks, whereas the WebClient's asyc API needs to be handled with events.
You can configure HttpClient to automatically pass credentials like this:
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
I was also having this same problem. I developed a synchronous solution thanks to the research done by #tpeczek in the following SO article: Unable to authenticate to ASP.NET Web Api service with HttpClient
My solution uses a WebClient, which as you correctly noted passes the credentials without issue. The reason HttpClient doesn't work is because of Windows security disabling the ability to create new threads under an impersonated account (see SO article above.) HttpClient creates new threads via the Task Factory thus causing the error. WebClient on the other hand, runs synchronously on the same thread thereby bypassing the rule and forwarding its credentials.
Although the code works, the downside is that it will not work async.
var wi = (System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity;
var wic = wi.Impersonate();
try
{
var data = JsonConvert.SerializeObject(new
{
Property1 = 1,
Property2 = "blah"
});
using (var client = new WebClient { UseDefaultCredentials = true })
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
}
}
catch (Exception exc)
{
// handle exception
}
finally
{
wic.Undo();
}
Note: Requires NuGet package: Newtonsoft.Json, which is the same JSON serializer WebAPI uses.
What you are trying to do is get NTLM to forward the identity on to the next server, which it cannot do - it can only do impersonation which only gives you access to local resources. It won't let you cross a machine boundary. Kerberos authentication supports delegation (what you need) by using tickets, and the ticket can be forwarded on when all servers and applications in the chain are correctly configured and Kerberos is set up correctly on the domain.
So, in short you need to switch from using NTLM to Kerberos.
For more on Windows Authentication options available to you and how they work start at:
http://msdn.microsoft.com/en-us/library/ff647076.aspx
OK, so thanks to all of the contributors above. I am using .NET 4.6 and we also had the same issue. I spent time debugging System.Net.Http, specifically the HttpClientHandler, and found the following:
if (ExecutionContext.IsFlowSuppressed())
{
IWebProxy webProxy = (IWebProxy) null;
if (this.useProxy)
webProxy = this.proxy ?? WebRequest.DefaultWebProxy;
if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null)
this.SafeCaptureIdenity(state);
}
So after assessing that the ExecutionContext.IsFlowSuppressed() might have been the culprit, I wrapped our Impersonation code as follows:
using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate())
using (System.Threading.ExecutionContext.SuppressFlow())
{
// HttpClient code goes here!
}
The code inside of SafeCaptureIdenity (not my spelling mistake), grabs WindowsIdentity.Current() which is our impersonated identity. This is being picked up because we are now suppressing flow. Because of the using/dispose this is reset after invocation.
It now seems to work for us, phew!
In .NET Core, I managed to get a System.Net.Http.HttpClient with UseDefaultCredentials = true to pass through the authenticated user's Windows credentials to a back end service by using WindowsIdentity.RunImpersonated.
HttpClient client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true } );
HttpResponseMessage response = null;
if (identity is WindowsIdentity windowsIdentity)
{
await WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, async () =>
{
var request = new HttpRequestMessage(HttpMethod.Get, url)
response = await client.SendAsync(request);
});
}
It worked for me after I set up a user with internet access in the Windows service.
In my code:
HttpClientHandler handler = new HttpClientHandler();
handler.Proxy = System.Net.WebRequest.DefaultWebProxy;
handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
.....
HttpClient httpClient = new HttpClient(handler)
....
Ok so I took Joshoun code and made it generic. I am not sure if I should implement singleton pattern on SynchronousPost class. Maybe someone more knowledgeble can help.
Implementation
//I assume you have your own concrete type. In my case I have am using code first with a class called FileCategory
FileCategory x = new FileCategory { CategoryName = "Some Bs"};
SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>();
test.PostEntity(x, "/api/ApiFileCategories");
Generic Class here. You can pass any type
public class SynchronousPost<T>where T :class
{
public SynchronousPost()
{
Client = new WebClient { UseDefaultCredentials = true };
}
public void PostEntity(T PostThis,string ApiControllerName)//The ApiController name should be "/api/MyName/"
{
//this just determines the root url.
Client.BaseAddress = string.Format(
(
System.Web.HttpContext.Current.Request.Url.Port != 80) ? "{0}://{1}:{2}" : "{0}://{1}",
System.Web.HttpContext.Current.Request.Url.Scheme,
System.Web.HttpContext.Current.Request.Url.Host,
System.Web.HttpContext.Current.Request.Url.Port
);
Client.Headers.Add(HttpRequestHeader.ContentType, "application/json;charset=utf-8");
Client.UploadData(
ApiControllerName, "Post",
Encoding.UTF8.GetBytes
(
JsonConvert.SerializeObject(PostThis)
)
);
}
private WebClient Client { get; set; }
}
My Api classs looks like this, if you are curious
public class ApiFileCategoriesController : ApiBaseController
{
public ApiFileCategoriesController(IMshIntranetUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
public IEnumerable<FileCategory> GetFiles()
{
return UnitOfWork.FileCategories.GetAll().OrderBy(x=>x.CategoryName);
}
public FileCategory GetFile(int id)
{
return UnitOfWork.FileCategories.GetById(id);
}
//Post api/ApileFileCategories
public HttpResponseMessage Post(FileCategory fileCategory)
{
UnitOfWork.FileCategories.Add(fileCategory);
UnitOfWork.Commit();
return new HttpResponseMessage();
}
}
I am using ninject, and repo pattern with unit of work. Anyways, the generic class above really helps.
Set identity's impersonation to true and validateIntegratedModeConfiguration to false in web.config
<configuration>
<system.web>
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
<identity impersonate="true"/>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" ></validation>
</system.webServer>
</configuration>
string url = "https://www..com";
System.Windows.Forms.WebBrowser webBrowser = new System.Windows.Forms.WebBrowser();
this.Controls.Add(webBrowser);
webBrowser.ScriptErrorsSuppressed = true;
webBrowser.Navigate(new Uri(url));
var webRequest = WebRequest.Create(url);
webRequest.Headers["Authorization"] = "Basic" + Convert.ToBase64String(Encoding.Default.GetBytes(Program.username + ";" + Program.password));
webRequest.Method = "POST";