Reputation: 21
Is it possible for an external site to POST to a Blazor Component (.razor)?
When the client is on my apps' shopping cart page and clicks on payment, he is redirected to an external site handling the payment. The external site then sends back a post request to my Blazor (.razor) component, to the url as specified in my component: @page "/mysite/success". This POST request fails status code 400.
If I add IgnoreAntiforgeryToken to my host page (RAZOR Page .cshtml) then I can post to my blazor component.
@page "/_Host"
@namespace Client
@attribute [IgnoreAntiforgeryToken]
@{
Layout = "_Layout";
}
@model HostModel
<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />
But I do not want to disable CSRF for all the components. Only for the one component.
Thanks, Louise
Upvotes: 2
Views: 4817
Reputation: 4981
Yes, it is. The trick to accepting the post is to include IgnoreAntiforgeryToken
in the _Host
page. The trick to reading the data is to catch the OnPost
in the PageModel
. The downfall is that Blazor is a single page application and the ignore attribute will be turned on globally.
I can't find an effective way to programmatically add the IgnoreAntiforgeryToken
or change the PageModel
for an individual page component. Instead I create a list of PostComponentPages
that will accept post data.
Hopefully someone can build on this answer and find a better way to turn on IgnoreAntiforgeryToken
for an individual component.
Note: In my example I only accept post data to the Counter page.
_Host.cshtml
@{
Layout = "_Layout";
@model HostPageModel;
}
HostPageModel.cs
// Required for accepting post data from 3rd party resource.
[IgnoreAntiforgeryToken]
// Not required, but gives us access to the post data and exception handling per request.
public class HostPageModel : PageModel
{
/// <summary>
/// A list of components we accept post request on.
/// </summary>
public static List<string> PostComponentPages = new List<string>() { "/counter" };
// postFormService is injected by the DI
public HostPageModel(PostFormService postFormService)
{
PostFormService = postFormService;
}
private PostFormService PostFormService { get; }
// Hook in to the OnPost.
public void OnPost()
{
if (PostComponentPages.Any(c => Request.Path.ToString().Contains(c)))
PostFormService.Form = Request.Form; // acceptable component, store the post form in the PostFormService
else
throw new Exception("HTTP 401 Error – Unauthorized");
}
}
PostFormService.cs
Responsible for storing the post data in a service collection.
public class PostFormService
{
public IFormCollection? Form { get; set; }
}
Program.cs
Register the service.
builder.Services.AddScoped<PostFormService>();
Counter.razor
Inject our service, and read the post data from third party.
@inject PostFormService PostFormService;
protected override void OnInitialized()
{
base.OnInitialized();
// These fields are based on the third party.
var PaymentID = PostFormService.Form?["PaymentID"];
var ReturnCode = PostFormService.Form?["ReturnCode"];
var ReturnMessage = PostFormService.Form?["ReturnMessage"];
}
Note: Just noticed this only works in ServerPrerendered
. Since the Post request is always treated as a new scope/session, I handle this by making the _Host.cshtml
force ServerPrerendered
for pages supporting Post requests.
@if (HostPageModel.PostComponentPages.Any(c => Request.Path.ToString().Contains(c)))
{
<component type="typeof(App)" render-mode="ServerPrerendered" />
}
else
{
<component type="typeof(App)" render-mode="Server" />
}
Upvotes: 4