401 unathorised when using windows authentication to access web services - c#

I have a asp.net mvc 4.0 web application that attempts to use windows authentication to authenticate against some web services. its hosted in iis7
The web services are autodesk vault 2011 web services, I present the user with a login screen where they can use a vault user account or they can use their windows login. There is no issue with using a vault account it logs in via the vault web services and call the various vault web services such as pulling data out of the vault. no issues here
When trying to log in using windows authentication then I get a 401 error when accessing the web services
I think that it could be a double hop issue so the server admins are going to enable Kerberos so we can at least eliminate this as a possibility. In the live environment the site and web services are located on different servers.
However for testing I have a virtual machine setup and the site and web services are on the same machine and I still get a 401 issue so maybe it is an iis configuration issue and not a double hop issue as originally thought
I have tried the following but with no success
I have added the following code to the winauthservice (I have wrapped the web services so that I can override the invoke and I have created a factory class to instantiate the services)
this.UnsafeAuthenticatedConnectionSharing = true;
var credentialCache = new CredentialCache();
credentialCache.Add(new Uri(this.Url), "NTLM", new NetworkCredential(identity.Name, identity.Password.ConvertToUNSecureString(), identity.Name.Substring(0, identity.Name.IndexOf("\\", StringComparison.OrdinalIgnoreCase))));
this.Credentials = credentialCache;
I have also tried it with Negotiate instead of NTLM
I have disabled the loopback check in the registry and I have added the server name and localhost to the BackConnectionHostNames registry entry
I have disabled anonymous authentication for the web site in iis and enabled windows authentication. In the providers for windows authentication I have removed negotiate and just left NTLM as saw a post recommending to do this.
In the web.config I have set authentication mode = windows
I have the following in my IIS logs
2013-09-25 09:11:05 fe80::9565:6102:fe2d:8f41%10 POST /AutodeskDM/Services/WinAuth/WinAuthService.asmx - 80 - fe80::9565:6102:fe2d:8f41%10 Mozilla/4.0+(compatible;+MSIE+6.0;+MS+Web+Services+Client+Protocol+4.0.30319.296) 401 2 5 11572
2013-09-25 09:11:05 fe80::9565:6102:fe2d:8f41%10 POST /AutodeskDM/Services/WinAuth/WinAuthService.asmx - 80 - fe80::9565:6102:fe2d:8f41%10 Mozilla/4.0+(compatible;+MSIE+6.0;+MS+Web+Services+Client+Protocol+4.0.30319.296) 401 1 3221225581 10
I'm running out of ideas as to what the issue could now be, but my thought is if I can get this working in my test vm then I may have more luck in the live environment

I had problems with authentication surviving a redirect when using a HttpWebRequest object, but I'm not sure if this is your problem. I noticed the redirect and the missing authentication using Fiddler2.
What I had to do was to set PreAuthenticate = true on the HttpWebRequest object
HttpWebRequest request;
request = (HttpWebRequest)WebRequest.Create(uri);
request.PreAuthenticate = true;
request.Credentials = new CredentialCache {{uri, "Basic", credentials}};
But, like I said, I'm not sure if this applies in your situation though.

Related

Blazor WASM authentication in Chrome Incognito

I do have a Blazor WASM (.NET 6) application that is using an Identity Server hosted on different URL for authentication.
I'm configuring the authentication like this:
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = configuration.OidcAuthority;
options.ProviderOptions.ClientId = configuration.OidcClientId;
options.ProviderOptions.ResponseType = configuration.OidcResponseType;
options.ProviderOptions.PostLogoutRedirectUri = configuration.OidcPostLogoutRedirectUri;
options.ProviderOptions.RedirectUri = configuration.OidcRedirectUri;
options.ProviderOptions.DefaultScopes = configuration.DefaultScopes;
});
Everything works great locally and in production as well.
The problem occurs when I want to open the page in Chrome Incognito window with Third party cookies disabled.
I see in the browser that during authorization process I receive cookies with SameSite=None and Secure=true, but this cookie is not send with
https://login.myidserver.net/connect/authorize?client_id=myclientid&redirect_uri=h...
as oppose to normal Chrome window.
Because of that I receive back
https://localhost:12345/authentication/login-callback?error=login_required&state=....
and access_token is not saved in session storage as it should be.
Is there any way to overcome this and force the cookie to be used in this case?
Or am I misunderstanding something?
All the solutions I've seen on the internet seems not to work for me.
Edit:
Ok, why is authorize called right after sucessful authentication and receiving of the token?. I understand I cannot force to send this cookie but can I somehow avoid do this authorize call when I'm right after successful login?

Single sign on using custom API

I want to implement a single sign on feature using my own API. Third party application(web application) will call this API and authenticate the users. For the communication between my API and other applications web request will be used. Below is the solution I provided for this,
I have created a API on my application and do the authentication based on request values. After successful authentication, I create the authentication cookie and add it to the response.
On the other app I used a HttpWebRequest and create CookieContainer. Then I get the cookies from response and assign those cookies to Response.
var response = (HttpWebResponse)http.GetResponse();
foreach (Cookie cook in response.Cookies)
{
Response.Cookies.Add(new System.Web.HttpCookie(cook.Name, cook.Value)
{
Domain = cook.Domain,
Expires = cook.Expires
});
}
In my test environment this works fine since both authentication API and other app are in same domain. But in customer testing phase this does not work due to domain mismatch. Because Authentication API is in different domain.
Is there any way to resolve this issue ?
I think it is impossible with cookies because they are domain bound and are not sent along with requests to domains. I guess you need see about other technology.
I hope this link will be useful
Good Luck

How do I setup a valid on-premise ADFS URI?

I have a .NET 4.6.2 Windows client application which needs to get an authentication token from our on-premise ADFS server and use it to call an ASP.NET Core REST API. It's client name, id (GUID) and re-direct URI have been registered with ADFS. I am using the latest ADAL (v3.13) library to facilitate the authentication. I am attempting to get a token as demonstrated in the ADAL sample code like this:
AuthenticationContext authenticationContext = new AuthenticationContext("https://<adfs-sts-server>/<rest-api-host>", false);
var result = authenticationContext.AcquireTokenAsync(<rest-api-resource-uri>, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));
The AcquireTokenAsync call returns an error, saying: The browser based authentication dialog failed to complete. Reason: The server has not found anything matching the requested URI (Uniform Resource Identifier).
Can anyone tell me:
Is the "requested URI" refered to in the error the https://<adfs-sts-server>/<rest-api-host> or <rest-api-resource-uri>?
Do I need to register <rest-api-host> or <rest-api-resource-uri> with ADFS in some way, and if so how?
Any other information I need to get this to work?
Thanks!
Peter
Using Active Directory Federation Services (ADFS) to provide authentication for on-premise endpoints from a Windows Client
Configuring ADFS
There are 2 parts to configuring ADFS.
Register the client application with ADFS
ADFS needs to be able to identify the application requesting user authentication, whether it be a service, WPF application, Web client or Office Add-in. I have gone generic and added the following client, which we can use for most of our C# requests; we may need to register a new client with different callback for Web clients.
Use one of the many tools out there to generate a GUID for the client ID.
* CLIENT_ID and APP_NAME should be unique.
* For a web client the redirect URI is where the auth service will redirect your call after authenticating the user. It should be an endpoint where you can process the token and continue with your client application. The redirect URI is not really used with rich clients/services/add-ins.
CLIENT_ID = 26E54EC9-7988-4DAE-A527-483A8A78B1C6
APP_NAME = Investplus
DESCRIPTION = Invest+ rich client suite
REDIRECT_URI = https://server/redirect-adfs.html
Instructions for Client registration
(may be possible in a wizard, but this is what I found on the web and it worked fo us)
Log on to the AD FS server as administrator and open a Windows PowerShell command window.
Enter the following command. In Windows PowerShell
Add-AdfsClient -ClientId <CLIENT_ID> -Name <APP_NAME> -RedirectUri <REDIRECT_URI>
Register the resource to be accessed ('Relying Party' in ADFS speak)
I found this link useful, it takes you through the steps of the wizard for setting up a relying party.
Instructions for Relying Party registration
The administrator on the server team will need to use the ADFS Add Relying Party Trust Wizard, and under the "Select Data Source" step select Enter data about the relying party manually.
Values you need to supply for this wizard:
DISPLAY_NAME = "MyInvestApi" (Unique display name for this Relying party)
PROFILE = "AD FS Profile"
ENABLE_SUPPORT_FOR_WS-FEDERATION_PASSIVE_PROTOCOL = true
URL = "https://server/api" (Unique URL for this RP)
ADD_ONE_OR_MORE_IDENTIFIERS = eg. "urn:myInvestApi" and "https://server/api"
ACCEPT_REMAINING_DEFAULTS
when given the opportunity, Add Claim Rules:
SEND_LDAP_ATTRIBUTES_AS_CLAIMS = true
ATTRIBUTE_STORE = Active Directory
SELECT_USEFUL_ATTRIBUTES = User-Principal-Name; Email; Display-Name
Configuring/Coding the Client application
Microsoft provides Active Directory Authentication Libraries (ADAL) for a range of platforms and languages from C# to Javascript, and from iOS to Cordova to Node.
The API exposed has changed significantly in each major version: I am using the latest C# library, currently 3.13.5.
The library makes the coding very simple, just a few lines; where I had problems was:
I couldn't find an explanation of what URL to use for the ADFS
Secure Token Service (STS)
I couldn't find documentation of the whole process as I am doing here (most documentation focussed on Azure FS), I struggled to work out
how the values provided to ADFS for Client and Relying party mapped
to the values used in the code.
What is the ADFS endpoint/URL to use in code?
Microsoft's best practice is to name your ADFS/STS server URL https://sts.domain.com (some people use https://adfs.domain.com, ask your server admins). However, if you try to hit this from a browser you'll get a 404 - Not found and trying to retrieve a token in the code, the ADAL library reports:
The browser based authentication dialog failed to complete. Reason: The server has not found anything matching the requested URI (Uniform Resource Identifier).
This is how I found the endpoint to use:
ADFS pubishes federation metadata at 'https://sts.domain.com/federationmetadata/2007-06/federationmetadata.xml'
Extract this file and open in a text editor.
When configuring the Relying Party, we specified "Enable Support for WS-Federation Passive Protocol" when specifying our resource endpoint, so search the XML for PassiveRequestorEndpoint.
Use the <Address> from this node - in my case https://sts.domain.com/adfs/ls/. I don't know if this will always be the value, or if it is specified when ADFS is setup and therefore potentially different per site.
What other values to use in the code?
We want our client app to retrieve a JSON Web Token (JWT) from ADFS which we can pass to our protected resource for authentication/authorization purposes.
At its most simple, the access token can be retrieved in 3 lines of code + configuration, and this will show how to translate what we have configured in ADFS to the values required by ADAL:
var stsEndpoint = "https://sts.domain.com/adfs/ls/";
var relyingPartyIdentifier = "urn:myInvestApi"; // Tenant in Azure AD speak, but this is an on-premise service
var authority = stsEndpoint + relyingPartyIdentifier;
var restResourceUrl = "https://server/api";
var redirectUri = "https://server/redirect-adfs.html";
const string CLIENT_ID = "26E54EC9-7988-4DAE-A527-483A8A78B1C6";
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
var asyncRequest = authenticationContext.AcquireTokenAsync(restResourceUrl, CLIENT_ID, redirectUri, new PlatformParameters(PromptBehavior.Auto));
var accessToken = asyncRequest.Result.AccessToken;
Useful references
ASP.NET Core Token Authentication Guide
ADAL - Native App to REST service - Authentication with ACS via Browser Dialog
Create a line-of-business Azure app with AD FS authentication
OAuth 2 Simplified
To issue the token for the web API, we need to make the ADFS to aware it by creating a relying party trust for the web API. And when we add a replying party we need to specify the identifiers for the replying party like figure below(Windows Server 2012 R2):
Then we can use this identifiers as the resource URI to acquire the token for this replying party. Please ensure that the resource URI is correct as you config like figure above.
And here is an article about developing with ADFS using OAuth:
Developing Modern Applications using OAuth and Active Directory Federation Services
Depending on the version of asdf, you may be able to use 'discovery' to obtain the endpoints to use.
Have a look at this post for more details: http://www.cloudidentity.com/blog/2015/08/21/openid-connect-web-sign-on-with-adfs-in-windows-server-2016-tp3/

Why is Windows Authentication working from local to server, but not server to server?

I have two sites, A and B. A consumes an API that B exposes, and B requires Windows authentication. Both sites live in Domain D.
The API is consumed via HttpClient, and when site A is run locally, under my domain account (which is in Domain P), access is granted. In this case, HttpClient is instantiated like so:
using(var client = new HttpClient(new HttpClientHandler { UseDefaultCredentials: true }))
When A is deployed to a testing server, the above results in a 401 Unauthorized response. The application pool on the testing server is running under a service account in domain D.
When explicitly using that service account like this:
var credential = new NetworkCredential("service-account", "password", "D");
var cache = new CredentialCache
{
{
new Uri(apiServerUri), "NTLM", credential
}
};
var handler = new HttpClientHandler
{
Credentials = cache
};
using(var client = new HttpClient(handler))
...
And again running site A locally, access is still granted. Access is also granted when accessing the API directly via browser, and specifying the service account credentials. Logs indicate that it is definitely the service account being used to access the API.
Deploying the above back to the testing server still results in 401 Unauthorized.
Deploying site A to a local instance of IIS, also successfully consumes the API of B.
Running site B locally, and then accessing it via site A locally, results in a 401 Unauthorized.
Accessing the API through a browser on the testing server where A is deployed, and specifying the service account credentials, also gives a 401 Unauthorized.
I'm not sure where to go from here - am I missing something in the code to get this working? Or is it likely to be an IIS or AD issue?
While I'm yet to determine exactly why this work around works, or if there is a better way of doing it (because this feels clunky), the following has allowed A to connect to B, when both are sitting on the same server.
Site B has had an additional host binding setup in IIS, to listen on localhost:12345. Site A has been configured to connect to that endpoint, rather than the domain name for Site B. Authentication is now working correctly.
I would be interested if anyone can explain why this is the case - I dislike 'magic' fixes.
edit
It would seem that this kb article is a likely cause for this behavior. Specifically:
When you use the fully qualified domain name (FQDN) or a custom host
header to browse a local Web site that is hosted on a computer that is
running Microsoft Internet Information Services (IIS) 5.1 or a later
version, you may receive an error message that resembles the
following: HTTP 401.1 - Unauthorized: Logon Failed This issue occurs
when the Web site uses Integrated Authentication and has a name that
is mapped to the local loopback address
and
Therefore, authentication fails if the FQDN or the custom host header that you use does not match the local computer name.
Registry modifications aren't really an option on these servers, so looks like the work around is what we will be using.

HttpClient & Windows Auth: Pass logged in User of Consumer to Service

I am struggling to understand and set up a Service and Consumer where the Service will run as the user logged into the Consumer.
My consumer is an MVC application. My Service is a Web Api application. Both run on separate servers within the same domain. Both are set to use Windows Auth.
My consumer code is:
private T GenericGet<T>(string p)
{
T result = default(T);
HttpClientHandler handler = new HttpClientHandler() { PreAuthenticate = true, UseDefaultCredentials = true };
using (HttpClient client = new HttpClient(handler))
{
client.BaseAddress = new Uri(serviceEndPoint);
HttpResponseMessage response = client.GetAsync(p).Result;
if (response.IsSuccessStatusCode)
result = response.Content.ReadAsAsync<T>().Result;
}
return result;
}
In my Service I call User.Identity.Name to get the caller ID but this always comes back as the consumer App Pool ID, not the logged in user. The consumer App Pool is running as a Network Service, the server itself is trusted for delegation. So how do I get the logged in User? Service code:
// GET: /Modules/5/Permissions/
[Authorize]
public ModulePermissionsDTO Get(int ModuleID)
{
Module module= moduleRepository.Find(ModuleID);
if (module== null)
throw new HttpResponseException(HttpStatusCode.NotFound);
// This just shows as the App Pool the MVC consumer is running as (Network Service).
IPrincipal loggedInUser = User;
// Do I need to do something with this instead?
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
ModulePermissionsDTO dto = new ModulePermissionsDTO();
// Construct object here based on User...
return dto;
}
According to this question, Kerberos is required to make this set up work because the HttpClient runs in a separate thread. However this confuses me because I thought the request sends an Authorization header and so the service should be able to use this and retrieve the user token. Anyway, I have done some testing with Kerberos to check that this correctly works on my domain using the demo in "Situation 5" here and this works but my two applications still wont correctly pass the logged in user across.
So what do I need to do to make this work? Is Kerberos needed or do I need to do something in my Service to unpack the Authorisation header and create a principal object from the token? All advice appreciated.
The key is to let your MVC application (consumer) impersonate the calling user and then issue the HTTP requests synchronously (i.e. without spawning a new thread). You should not have to concern yourself with low-level implementation details, such as NTLM vs Kerberos.
Consumer
Configure your MVC application like so:
Start IIS Manager
Select your MVC web application
Double click on 'Authentication'
Enable 'ASP.NET Impersonation'
Enable 'Windows Authentication'
Disable other forms of authentication (unless perhaps Digest if you need it)
Open the Web.config file in the root of your MVC application and ensure that <authentication mode="Windows" />
To issue the HTTP request, I recommend you use the excellent RestSharp library. Example:
var client = new RestClient("<your base url here>");
client.Authenticator = new NtlmAuthenticator();
var request = new RestRequest("Modules/5/Permissions", Method.GET);
var response = client.Execute<ModulePermissionsDTO>(request);
Service
Configure your Web API service like so:
Start IIS Manager
Select your Web API service
Double click on 'Authentication'
Disable 'ASP.NET Impersonation'.
Enable 'Windows Authentication'
If only a subset of your Web API methods requires users to be authenticated, leave 'Anonymous Authentication' enabled.
Open the Web.config file in the root of your Web API service and ensure that <authentication mode="Windows" />
I can see that you've already decorated your method with a [Authorize] attribute which should trigger an authentication challenge (HTTP 401) when the method is accessed. Now you should be able to access the identity of your end user through the User.Identity property of your ApiController class.
The key issue with double hop is delegation of user credential to second call. I want to elaborate a little bit about it. C1 = client browser , S1 = First Server , S2 = Second Server.
Suppose our complete system support window authentication. When user access S1 from browser , its default window credential pass to server S1, but when S1 make a call to S2 , by default it don't pass credential to S2.
Resolution :
We must enable window authentication/ impersonation on both machines.
WE need to enable delegation between server so that S1 can trust to S2 and will pass credential to S2.
You can find some useful details at below links :
http://blogs.msdn.com/b/farukcelik/archive/2008/01/02/how-to-set-up-a-kerberos-authentication-scenario-with-sql-server-linked-servers.aspx
https://sqlbadboy.wordpress.com/2013/10/11/the-kerberos-double-hop-problem/
If you are trying to access service which is hosted on windows authentication then do following.
var request = new RestRequest(Method.POST);
If you want to use applications default credentials which must have access on hosted service server
request.UseDefaultCredentials = true;
or user below to pass the credentials manually
request.Credentials = new NetworkCredential("Username", "Password", "Domain");

Categories

Resources