简体   繁体   中英

Problem accessing the current HttpContext in Razor Component ASP.NET Core

I have an issue accessing the HTTPContext consistently. I need to access the HTTPContext from my razor component pages. I am accessing the HTTPContext from the OnInitialized() event in order to read a cookie and a session string. My issue is the OnInitialized() event is invoked twice. The first time both the cookie and session string are read normally. The second invoke the HttpContextAccessor.HttpContext returns null and neither the cookie and session variable can be accessed. This only occurs in the staging environment it works properly in the development environment.

Here is my razor page:

@page "/certcheck5"

<PageTitle>Certificate Check</PageTitle>

@using System.Diagnostics;
@using Microsoft.Extensions.Options;
@using Microsoft.AspNetCore.Http;
@using Microsoft.Extensions.Logging;
@using System.Security.Cryptography.X509Certificates;
@using CIS_2.Helpers;


@inject ILogger<SearchParameters> Logger;
@inject IHttpContextAccessor HttpContextAccessor;

<h1>Certificate Check</h1>

<p>
    Retrieves HttpContext.Connection.ClientCertificate.<br>
    Enumerates Session Variable.<br><br>
    <label>
        <input type="checkbox" @bind="shouldRender" />
        ShouldRender?
    </label>
</p>

<p>
    <div>Counter: @iCnt.ToString()</div>
    <button @onclick="IncrementCount">Increment Counter</button>
</p>

<p>
    <h4>HttpContext</h4>
    <div>USER CERT Subject: @Subject</div>
    <div>USER CERT Thumbprint: @Thumbprint</div>
    <div>USER CERT Expiry Date: @Expires</div>
    <div>REQUEST COOKIE[AuthCookie]: @AuthCookie</div>
</p>

<p>
    <h4>Session Parameters</h4>
    <div>Status: @Status</div>
    <div>SessionVar: @SessionVar</div>
</p>

@code {

    private bool shouldRender = true;
    private bool bLoading = true;
    private X509Certificate2 UserCert;
    private string Subject = string.Empty;
    private string Thumbprint = string.Empty;
    private string Expires = string.Empty;
    private string AuthCookie = string.Empty;
    private string SessionVar = string.Empty;
    private string Status = string.Empty;
    private int iCnt = 0;

    protected override void OnInitialized()
    {
        Logger.LogWarning("***CertCheck5.OnInitialized() invoke");
        string auth_Cookie = string.Empty;
        try
        {
            auth_Cookie = HttpContextAccessor.HttpContext.Request.Cookies["FMLO-auth"];

            Status = "Starting";
            Status = HttpContextAccessor.HttpContext.ToString();
            Status = HttpContextAccessor.HttpContext.Session.ToString();
            SessionVar = HttpContextAccessor.HttpContext.Session.GetString("SearchParameters");
            Debug.WriteLine("***Session.Restore() worked");
            Logger.LogWarning("***Session.Restore() worked");
        }
        catch (Exception exc)
        {
            Logger.LogWarning("***Session.Restore() failed:" + exc.Message);
        }

        if (HttpContextAccessor.HttpContext is not null)
        {
            X509Certificate2 UserCert = HttpContextAccessor.HttpContext.Connection.ClientCertificate;

            try
            {
                // This is called more than once the cookie is null the first time.
                auth_Cookie = HttpContextAccessor.HttpContext.Request.Cookies["FM-auth"];
            }
            catch (Exception exc)
            { Logger.LogWarning("***HttpContextAccessor.HttpContext.Request.Cookies failed:" + exc.Message); }

            if (auth_Cookie is not null)
            {
                if (Subject.Length == 0) Subject = UserCert.Subject.ToString();
                if (Thumbprint.Length == 0) Thumbprint = UserCert.Thumbprint;
                if (Expires.Length == 0) Expires = UserCert.NotAfter.ToString();
                if (AuthCookie.Length == 0) AuthCookie = auth_Cookie;
            }
        }
    }

    protected override bool ShouldRender()
    {
        Debug.WriteLine("***ShouldRender");
        Logger.LogWarning("***ShouldRender");
        return shouldRender;
        //return base.ShouldRender();
    }

    private void IncrementCount()
    {
        iCnt++;
    }
}

Also, the _Host.cshtml has the render-mode=”ServerPrerendered” hence the dual calls to OnInitialized(); when I change to render-mode=”Server” then I can no longer read the cookie. We are using smartcards to authenticate users.

Here's my program.cs file

using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System.IO;
using System.Diagnostics;
using Microsoft.AspNetCore.StaticFiles.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System;

WebApplicationBuilder builder;

builder = WebApplication.CreateBuilder(
    new WebApplicationOptions
    {
        ContentRootPath = Directory.GetCurrentDirectory(),
        WebRootPath = Directory.GetCurrentDirectory() + "\\wwwroot"
    }
    );
// Add services to the container.

builder.Services.AddAuthentication(
    CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.All;
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                //Leveraging the DP injected single instance of the CertificateValidation Service
                var validationService = context.HttpContext.RequestServices.GetService<ICertValidation>();
                // Here it validate and call the methode ValidateCertificate from CertificateValidationService class.
                if (validationService.ValidateCertificate(context.ClientCertificate))
                {
                    context.Success();
                }
                else
                {
                    context.Fail("Invalid certificate");
                }
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                context.Fail("Invalid certificate");
                return Task.CompletedTask;
            }
        };
    });

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddRazorPages();
builder.Services.AddControllers();
builder.Services.AddServerSideBlazor();
builder.Services.AddSession(options =>
{
    options.Cookie.IsEssential = true;
});

builder.Services.AddDistributedMemoryCache();
builder.Services.AddMvc();

builder.Services.AddSingleton<ICertValidation, CertificateValidationService>();
builder.Services.AddHttpContextAccessor();

builder.Services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "X-SSL-CERT";

    options.HeaderConverter = headerValue =>
    {
        X509Certificate2? clientCertificate = null;

        if (!string.IsNullOrWhiteSpace(headerValue))
        {
            clientCertificate = new X509Certificate2(StringToByteArray(headerValue));
        }

        return clientCertificate!;

        static byte[] StringToByteArray(string hex)
        {
            var numberChars = hex.Length;
            var bytes = new byte[numberChars / 2];

            for (int i = 0; i < numberChars; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            }

            return bytes;
        }
    };
});

builder.Services.Configure<CisSettings>(builder.Configuration.GetSection("Settings"));

var app = builder.Build();

app.UseAuthentication();
//app.UseAuthorization();

var sPDF_Physicialroot = builder.Configuration["Settings:PDF_Physicialroot"];
var sPDF_InvoiceBaseURL = builder.Configuration["Settings:PDF_InvoiceBaseURL"];


// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseStaticFiles();

// this adds a folder that will render static files from "/pdf_invoices (PDF_InvoiceBaseURL)
// wwwroot\cis\PDF_Invoices
SharedOptions sharedOptions = new SharedOptions();
sharedOptions.FileProvider = new PhysicalFileProvider(sPDF_Physicialroot);
sharedOptions.RequestPath = new PathString(sPDF_BaseURL);
StaticFileOptions staticFileOptions = new StaticFileOptions(sharedOptions);
app.UseStaticFiles(staticFileOptions);
app.Logger.LogWarning("Trace Message from Program.cs");

app.UseSession();

//begin SetString() hack
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
    //this throwaway session variable will "prime" the SetString() method
    //to allow it to be called after the response has started
    var TempKey = Guid.NewGuid().ToString(); //create a random key
    Context.Session.Set(TempKey, Array.Empty<byte>()); //set the throwaway session variable
    Context.Session.Remove(TempKey); //remove the throwaway session variable
    string auth_Cookie = $"None";
    Context.Request.Cookies.TryGetValue("FM-auth", out auth_Cookie);
    string msg = "Program.cs auth_Cookie=" + auth_Cookie;
    app.Logger.LogWarning(msg);
    SessionParameters sessionParameters;
    ISearchParameters searchParameters;
    searchParameters = new SearchParameters(app.Logger);
    sessionParameters = new SessionParameters(app.Logger);
    if (!sessionParameters.Exists(Context.Session))
    {
        searchParameters.OnInitialized(Uics, auth_Cookie);
        sessionParameters.StoreMain(Context.Session, searchParameters);
    }
    await Next(); //continue on with the request
});
//end SetString() hack

Debug.WriteLine($"Content Root Path: {builder.Environment.ContentRootPath}");
Debug.WriteLine($"WebRootPath:  {builder.Environment.WebRootPath}");
Debug.WriteLine($"PDFRootPath:  {sPDF_Physicialroot}");

app.UseRouting();

app.UseEndpoints(endpoints =>
{
    app.MapControllers();
    app.MapBlazorHub();
    app.MapFallbackToPage("/_Host");
}
);

app.Run();

You shouldn't use HttpContextAccessor in Blazor Server because the Blazor Server works outside the.NetCore pipeline and basically there is no guarantee that you will have access to the desired amount of HttpContext everywhere for more info you can refer to this issue and also this doc . However, If you have to use the HttpContext then you have to get the desired value(s) from HttpContext when rendering _Host.cshtml and save it in a variable and use that variable in the form of Cascading Parameters in the components in the rest of the program.

An example of how to implement the use of cookies in Blazor Server is placed here .

I also implemented a Blazor Server app based on Cookie Authentication here . I hope that it help you

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM