kestrel .net5 dateHeaderValues is null
See original GitHub issueHi,
I’ve been breaking my head on this issue for 2 days now, I followed the sample on IdentityModel.OidcClient.Samples to implement a local server to handle the oidc callback page. but I keep running into the issue of a null reference exception inside kestrel itself. Having delved deeper into this issue I have identified the source to be on aspnetcore/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs:1188. Looking into the call I have a suspicion it is “simply” a racing condition. since I have modified the sample a bit I’ll post it below.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using IdentityModel.OidcClient;
using IdentityModel.OidcClient.Browser;
using IdentityModel.OidcClient.Results;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Serilog;
using Serilog.Sinks.SystemConsole.Themes;
namespace Fyn.Windows.Service
{
    public static class Authentication
    {
        private static readonly String Authority = "https://unifyned.cloud";
        private static readonly String Api = "https://unifyned.cloud/v1/cms/file";
        private static OidcClient? oidcClient;
        private static HttpClient apiClient = new HttpClient { BaseAddress = new Uri(Api), DefaultRequestVersion = new Version(2, 0) };
        public static async ValueTask Signin()
        {
            SystemBrowser browser = new SystemBrowser(5002);
            String redirectUri = $"http://127.0.0.1:{browser.Port}";
            OidcClientOptions options = new OidcClientOptions
            {
                Authority = Authority,
                ClientId = "Shell.Windows",
                RedirectUri = redirectUri,
                Scope = "openid profile email",
                FilterClaims = false,
                Browser = browser,
                IdentityTokenValidator = new JwtHandlerIdentityTokenValidator(),
                RefreshTokenInnerHttpHandler = new HttpClientHandler(),
            };
            options.LoggerFactory.AddSerilog(
                new LoggerConfiguration()
                    .MinimumLevel.Debug()
                    .Enrich.FromLogContext()
                    .WriteTo.Console(
                        outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}", 
                        theme: AnsiConsoleTheme.Code
                    )
                    .CreateLogger()
            );
            oidcClient = new OidcClient(options);
            LoginResult? result = await oidcClient.LoginAsync();
            apiClient = new HttpClient(result.RefreshTokenHandler)
            {
                BaseAddress = new Uri(Api),
            };
            result.Show();
            await result.NextSteps();
        }
        private static void Show(this LoginResult result)
        {
            if (result.IsError)
            {
                Console.WriteLine($"\n\nError:\n{result.Error}");
                return;
            }
            Console.WriteLine("\n\nClaims:");
            foreach (Claim claim in result.User.Claims)
            {
                Console.WriteLine($"{claim.Type}: {claim.Value}");
            }
            Dictionary<String, JsonElement>? values = JsonSerializer.Deserialize<Dictionary<String, JsonElement>>(result.TokenResponse.Raw);
            Console.WriteLine("token response...");
            if (values == null)
            {
                return;
            }
            foreach ((String key, JsonElement value) in values)
            {
                Console.WriteLine($"{key}: {value}");
            }
        }
        private static async ValueTask NextSteps(this LoginResult result)
        {
            String currentAccessToken = result.AccessToken;
            String currentRefreshToken = result.RefreshToken;
            String menu = "  x...exit  c...call api   ";
            if (currentRefreshToken != null)
            {
                menu += "r...refresh token   ";
            }
            while (true)
            {
                Console.WriteLine("\n\n");
                Console.Write(menu);
                ConsoleKeyInfo key = Console.ReadKey();
                switch (key.Key)
                {
                    case ConsoleKey.X:
                    {
                        return;
                    }
                    case ConsoleKey.C:
                    {
                        await CallApi();
                        break;
                    }
                    case ConsoleKey.R:
                    {
                        RefreshTokenResult refreshResult = await oidcClient.RefreshTokenAsync(currentRefreshToken);
                        if (refreshResult.IsError)
                        {
                            Console.WriteLine($"Error: {refreshResult.Error}");
                        }
                        else
                        {
                            currentRefreshToken = refreshResult.RefreshToken;
                            currentAccessToken = refreshResult.AccessToken;
                            Console.WriteLine("\n\n");
                            Console.WriteLine($"access token:   {currentAccessToken}");
                            Console.WriteLine($"refresh token:  {currentRefreshToken ?? "none"}");
                        }
                        break;
                    }
                }
            }
        }
        private static async ValueTask CallApi()
        {
            HttpResponseMessage response = await apiClient.GetAsync("");
            if (response.IsSuccessStatusCode)
            {
                JsonDocument json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
                Console.WriteLine("\n\n");
                Console.WriteLine(json.RootElement);
            }
            else
            {
                Console.WriteLine($"Error: {response.ReasonPhrase}");
            }
        }
    }
    public class SystemBrowser : IBrowser
    {
        public Int32 Port { get; }
        private readonly String _path;
        public SystemBrowser(Int32? port = null, String? path = null)
        {
            _path = path;
            Port = port ?? GetRandomUnusedPort();
        }
        private static Int32 GetRandomUnusedPort()
        {
            TcpListener listener = new TcpListener(IPAddress.Loopback, 0);
            listener.Start();
            Int32 port = ((IPEndPoint)listener.LocalEndpoint).Port;
            listener.Stop();
            return port;
        }
        public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken)
        {
            await using LoopbackHttpListener listener = new LoopbackHttpListener(Port, _path);
            await listener.Start();
            OpenBrowser(options.StartUrl);
            try
            {
                String? result = await listener.WaitForCallbackAsync();
                return String.IsNullOrWhiteSpace(result) 
                    ? new BrowserResult
                    {
                        ResultType = BrowserResultType.UnknownError, 
                        Error = "Empty response.",
                    } 
                    : new BrowserResult
                    {
                        ResultType = BrowserResultType.Success, 
                        Response = result,
                    };
            }
            catch (TaskCanceledException ex)
            {
                return new BrowserResult
                {
                    ResultType = BrowserResultType.Timeout, 
                    Error = ex.Message,
                };
            }
            catch (Exception ex)
            {
                return new BrowserResult
                {
                    ResultType = BrowserResultType.UnknownError, 
                    Error = ex.Message,
                };
            }
        }
        public static void OpenBrowser(String url)
        {
            // hack because of this: https://github.com/dotnet/corefx/issues/10361
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                url = url.Replace("&", "^&");
                Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                Process.Start("xdg-open", url);
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                Process.Start("open", url);
            }
            else
            {
                Process.Start(url);
            }
        }
    }
    public class LoopbackHttpListener : IAsyncDisposable
    {
        const Int32 DefaultTimeout = 300_000;
        private readonly IWebHost _host;
        private readonly TaskCompletionSource<String> _source = new TaskCompletionSource<String>();
        public LoopbackHttpListener(Int32 port, String? path = null)
        {
            _host = new WebHostBuilder()
                .UseUrls($"http://127.0.0.1:{port}/{path?.TrimStart('/')}")
                .UseKestrel()
                .Configure(builder =>
                {
                    builder.Run(async context =>
                    {
                        switch (context.Request.Method)
                        {
                            case "GET":
                            {
                                await SetResult(context.Request.QueryString.Value, context);
                                break;
                            }
                            case "POST" when !context.Request.ContentType.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase):
                            {
                                context.Response.StatusCode = 415;
                                break;
                            }
                            case "POST":
                            {
                                using StreamReader sr = new StreamReader(context.Request.Body, Encoding.UTF8);
                                await SetResult(await sr.ReadToEndAsync(), context);
                                break;
                            }
                            default:
                            {
                                context.Response.StatusCode = 405;
                                break;
                            }
                        }
                    });
                })
                .ConfigureLogging(options =>
                {
                    options.AddSerilog(
                        new LoggerConfiguration()
                            .MinimumLevel.Debug()
                            .Enrich.FromLogContext()
                            .WriteTo.Console(
                                outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}",
                                theme: AnsiConsoleTheme.Code
                            )
                            .CreateLogger()
                    );
                })
                .Build();
        }
        public Task Start()
        {
            return _host.StartAsync();
        }
        public async ValueTask DisposeAsync()
        {
            await Task.Delay(500);
            _host.Dispose();
        }
        private async ValueTask SetResult(String value, HttpContext context)
        {
            try
            {
                context.Response.StatusCode = 200;
                context.Response.ContentType = "text/html";
                await context.Response.WriteAsync("<h1>You can now return to the application.</h1>");
                await context.Response.Body.FlushAsync();
                _source.TrySetResult(value);
            }
            catch(Exception exception)
            {
                context.Response.StatusCode = 400;
                context.Response.ContentType = "text/html";
                await context.Response.WriteAsync("<h1>Invalid request.</h1>");
#if DEBUG
                await context.Response.WriteAsync($"<p>{exception.Message}</p>");
                await context.Response.WriteAsync($"<p>{exception.StackTrace}</p>");
#endif
                await context.Response.Body.FlushAsync();
            }
        }
        public async ValueTask<String> WaitForCallbackAsync(Int32 timeout = DefaultTimeout)
        {
            await Task.Delay(timeout);
            _source.TrySetCanceled();
            return await _source.Task;
        }
    }
}
It is the this line which triggers the exception
await context.Response.WriteAsync("<h1>You can now return to the application.</h1>");
Am I just an idiot and blind for a missing await somewhere?
Is the Heartbeat in kestrel internally broken?
Is my config of the WebHost correct?
Thank you in advance!
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:16 (10 by maintainers)

 Top Related Medium Post
Top Related Medium Post Top Related StackOverflow Question
Top Related StackOverflow Question
Wooooooooooooo
exactly the same
One difference is that I try to run multiple Hosts while using
PackageReferencein a single Console app. The problem disappears after modified toFrameworkReference. (Commit)I’d recommend reading this https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-5.0&tabs=visual-studio#framework-reference