How does Windows authentication in the web work?

So I created a Web Api for the test.

I appeal to ValuesController and in some magical way my credentials fall into the controller.

How do they get there? Does their browser broadcast? How does he know that he should attach them?

  • More if possible. Probably, if the usual Basic or Digest http authorization to a specific address passes, and you have visited this resource before, logged in and saved the password .. :) And in the webApi settings, authorization is set using attributes, check this point. - NewView
  • NTLM | NTLMv2 | Kerberos . Judging by the documentation , it uses the default NTLM . - EvgeniyZ

1 answer 1

As the browser and the server agree on the protocol during the handshake.

It is very curious to pick the source code of owin or some third-party web server easier (I love NancyFX). For example, the server can work in one of the following modes:

  • Anonymous
  • Basic
  • Digest
  • IntegratedWindowsAuthentication
  • Negotiate
  • None
  • Ntlm

Example NancyFX code for domain authorization:

 using System.Net; using Owin; namespace NancyWebApp.Nancy.Owin { public class Startup { public void Configuration(IAppBuilder app) { var listener = (HttpListener) app.Properties["System.Net.HttpListener"]; listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication; app.UseNancy(); } } } 

Most likely to be Kerberos or NTLM. Yes, yes, I myself was surprised that the old man ntlm is still alive.

I somehow understood a similar question - you can see the links on MSDN, where all the steps to establish the protocol and the format of the forwarded messages are detailed in detail.

Separately, it must be borne in mind that if you are viewing the headers in the browser - be prepared for the fact that chrome is deceiving and shows not everything.

Also, you need to separately understand that in many cases, in the case of CORS requests, before the actual POST request, the browser likes to send preliminary OPTIONS headers - and this is also confusing at first when debugging the WebAPI. (Perhaps you have already encountered? I do not know, but I will mention)

It is very curious to simulate a real browser from HttpClient and compare the behavior with a real browser - it looks like everything in fiddler (Frankly, installing fiddler is also a tambourine dance when it comes to the HTTPS protocol, there are three There are underwater stones that will have to go through).

Here's an example for NTLM:

 public class WebRequestHelper { public WebRequestHelper(string userName, string password, string domain) { var credentials = new NetworkCredential(userName, password, domain); var handler = new HttpClientHandler { Credentials = credentials, UseDefaultCredentials = false }; this.Client = new HttpClient(handler); } public HttpClient Client { get; set; } public async Task<string> GetAsync(string uri) { return await this.Client.GetStringAsync(uri); } public async Task<string> PostFormAsync(string uri, Dictionary<string, string> data) { var content = new FormUrlEncodedContent(data); var response = await this.Client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task<string> PostAsync(string uri, string jsonString) { var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); var response = await this.Client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } } 

We prepare WebApi, we fire:

 var unit = new WebRequestHelper("AK", "password", "domain"); unit.GetAsync("https://localhost:44395/api/values").Result.Dump(); // Expected: ["value1","value2"] 

And here is my linqpad script for JWT:

 public class WebRequestHelper { public async Task<string> GetAsync(string uri) { var client = new HttpClient(); return await client.GetStringAsync(uri); } public async Task<string> PostFormAsync(string uri, Dictionary<string, string> data) { var client = new HttpClient(); var content = new FormUrlEncodedContent(data); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task<string> PostAsync(string uri, string jsonString) { var client = new HttpClient(); var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task<Token> GetToken(string tokenUrl, string username, string password) { var data = new Dictionary<string, string> { { "username", username }, { "password", password }, }; var answer = await this.PostFormAsync(tokenUrl, data); if(string.Equals(answer, "Invalid username or password.")) return null; return JsonConvert.DeserializeObject<Token>(answer); } public async Task<string> GetWithJwtAsync(string uri, Token token) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); return await client.GetStringAsync(uri); } public async Task<string> PostFormWithJwtAsync(string uri, Dictionary<string, string> data, Token token) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("JWT", token.Value); var content = new FormUrlEncodedContent(data); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task<string> PostWithJwtAsync(string uri, string jsonString, Token token) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } } public class Token { [JsonProperty("access_token")] public string Value { get; set; } [JsonProperty("username")] public string UserName { get; set; } } 

And it's very good to connect the swagger to the server side to look at the API you are creating.

  • Another question, maybe for a separate question, but maybe the answer will be short: Here the client sent the credentials, the server checked them. Do I need to return any token. I, as I understand, if you do not use Tokens, then the client with each request will be forced to send credentials? - iluxa1810
  • @ iluxa1810 For Basic authorization, you don’t need to return anything; for NTLM, the web server itself returns a session cookie (before closing the browser), you directly give the token in the case of JWT tokens. Here is the code giving the token ( tynts , tynts ) - about the same code in the 3rd party server of type IdentityServer. And the client sends them! You open any corporate portal in the browser and study the headers to be sent. I wrote about five applications with different types of authorization, while I was preparing a real API - AK
  • Are these session cookies stored in server memory? - iluxa1810 pm
  • @ iluxa1810 This is a difficult question, I'm not sure that I can simply, correctly and clearly explain. Read the documentation better, how the domain controller generates them and how the web server validates it and can it do it in some situations without a domain controller. Cases are different, one word is not answered. - AK