{"action":"opened","issue":{"url":"https://api.github.com/repos/dotnet/corefx/issues/24387","repository_url":"https://api.github.com/repos/dotnet/corefx","labels_url":"https://api.github.com/repos/dotnet/corefx/issues/24387/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/corefx/issues/24387/comments","events_url":"https://api.github.com/repos/dotnet/corefx/issues/24387/events","html_url":"https://github.com/dotnet/corefx/issues/24387","id":262258563,"number":24387,"title":"ManagedHandler: AuthenticationHandler clears the wrong credentials field on redirect","user":{"login":"Tratcher","id":1821173,"avatar_url":"https://avatars3.githubusercontent.com/u/1821173?v=4","gravatar_id":"","url":"https://api.github.com/users/Tratcher","html_url":"https://github.com/Tratcher","followers_url":"https://api.github.com/users/Tratcher/followers","following_url":"https://api.github.com/users/Tratcher/following{/other_user}","gists_url":"https://api.github.com/users/Tratcher/gists{/gist_id}","starred_url":"https://api.github.com/users/Tratcher/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tratcher/subscriptions","organizations_url":"https://api.github.com/users/Tratcher/orgs","repos_url":"https://api.github.com/users/Tratcher/repos","events_url":"https://api.github.com/users/Tratcher/events{/privacy}","received_events_url":"https://api.github.com/users/Tratcher/received_events","type":"User","site_admin":false},"labels":[{"id":497898703,"url":"https://api.github.com/repos/dotnet/corefx/labels/area-System.Net.Http","name":"area-System.Net.Http","color":"d4c5f9","default":false},{"id":145399955,"url":"https://api.github.com/repos/dotnet/corefx/labels/bug","name":"bug","color":"207de5","default":true}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2017-10-02T23:19:57Z","updated_at":"2017-10-02T23:19:57Z","closed_at":null,"author_association":"NONE","body":"https://github.com/dotnet/corefx/blob/f74844b5b90ca9f3fda8439349071f549df03d43/src/System.Net.Http/src/System/Net/Http/Managed/AuthenticationHandler.cs#L48-L54\r\n\r\nThe AuthenticationHandler checks if the result was a redirect, and if so it clears the _credentials field. However, the lifetime of the AuthenticationHandler object and the _credentials field is not per request, it's for the entire lifetime of the HttpClient and associated handlers pipeline. The first request to experience a redirect ends up clearing the credentials for all subsequent requests, not just for the redirect. The other handlers have similar logic but they maintain a per-request state object with a copy of _credentials that can be cleared per request.\r\n\r\nThis redirect detection logic also needs to be moved to the bottom of the method so it also detects redirects from the secondary login requests.\r\n\r\nRepro code:\r\ndotnet --version:\r\n2.1.0-preview1-007228\r\n\r\nOutput for Non-ManagedHandler: 401 and then 200.\r\nOutput for ManagedHandler: 401 and 401.\r\n\r\n```\r\nusing System;\r\nusing System.Net;\r\nusing System.Net.Http;\r\nusing System.Text;\r\nusing System.Threading.Tasks;\r\n\r\nnamespace ConsoleApp16\r\n{\r\n    class Program\r\n    {\r\n        static void Main(string[] args)\r\n        {\r\n            var listener = new HttpListener();\r\n            listener.Prefixes.Add(\"http://localhost:5001/\");\r\n            listener.Start();\r\n\r\n            RunServer(listener);\r\n\r\n            RunClient().Wait();\r\n        }\r\n\r\n        private static async Task RunClient()\r\n        {\r\n            var handler = new HttpClientHandler();\r\n            handler.Credentials = new NetworkCredential(\"foo\", \"bar\");\r\n            var client = new HttpClient(handler);\r\n            var response = await client.GetAsync(\"http://localhost:5001/redirect\");\r\n            Console.WriteLine(response);\r\n            Console.WriteLine(await response.Content.ReadAsStringAsync());\r\n            var response2 = await client.GetAsync(\"http://localhost:5001/basic\");\r\n            Console.WriteLine(response2);\r\n            Console.WriteLine(await response2.Content.ReadAsStringAsync());\r\n        }\r\n\r\n        private static async void RunServer(HttpListener listener)\r\n        {\r\n            while (true)\r\n            {\r\n                var context = await listener.GetContextAsync();\r\n                if (context.Request.Url.AbsolutePath.Equals(\"/basic\"))\r\n                {\r\n                    var authHeader = context.Request.Headers[\"Authorization\"];\r\n                    if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith(\"Basic \"))\r\n                    {\r\n                        var bytes = Encoding.UTF8.GetBytes($\"Auth header after login: {authHeader}\");\r\n                        await context.Response.OutputStream.WriteAsync(bytes, 0, bytes.Length);\r\n                    }\r\n                    else\r\n                    {\r\n                        // Not authenticated\r\n                        context.Response.StatusCode = 401;\r\n                        context.Response.Headers[HttpResponseHeader.WwwAuthenticate] = \"Basic\";\r\n                    }\r\n                }\r\n                else if (context.Request.Url.AbsolutePath.Equals(\"/redirect\"))\r\n                {\r\n                    context.Response.Redirect(\"/basic\");\r\n                }\r\n                else\r\n                {\r\n                    context.Response.StatusCode = 404;\r\n                }\r\n                context.Response.Close();\r\n            }\r\n        }\r\n    }\r\n}\r\n```"}}
{"actor":{"display_login":"Tratcher"}}