dotnet / AspNetCore.Docs.Samples

Creative Commons Attribution 4.0 International
171 stars 149 forks source link

Antiforgery integration for minimal APIs #173

Open Rick-Anderson opened 1 year ago

Rick-Anderson commented 1 year ago

See Antiforgery integration for minimal APIs

and this sample

In this code I pass in the action so

app.MapGet("/DisableAntiforgery", () =>
{
    return Results.Content(MyHtml.html("/todo"), "text/html");
});

app.MapGet("/post2", () =>
{
    return Results.Content(MyHtml.html("/todo2"), "text/html");
});

Can use the same HTML when they pass in the action arg.

Is there a clean way to do this to pass in an arg for

<input name="{token.FormFieldName}" 
                              type="hidden" value="{token.RequestToken}" />

Maybe that makes things too messy. Perhaps I should duplicate most of the HTML and not get the HTML from a method while passing the action.

public static string html2(string action, AntiforgeryTokenSet token) => $"""
    <html><body>
        <form action="{action}" method="POST" enctype="multipart/form-data">
            <input name="{token.FormFieldName}" 
                          type="hidden" value="{token.RequestToken}" />
            <input type="text" name="name" />
            <input type="date" name="dueDate" />
            <input type="checkbox" name="isCompleted" />
            <input type="submit" />
        </form>
    </body></html>
""";

Works, but I'm not sure it's an improvement. There's no way to pass in a null token.

@sammychinedu2ky @david-acker

cc @guardrex

sammychinedu2ky commented 1 year ago

I think this works @Rick-Anderson

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();

app.MapGet("/antiforgery", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    return Results.Content(MyHtml.html("/todo", token), "text/html");
});

app.MapGet("/non-antiforgery", () =>
{
    Console.WriteLine("nonantiforgery");
    return Results.Content(MyHtml.html("/todo2"), "text/html");
});

app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
                                                .DisableAntiforgery();

app.Run();

class Todo
{
    public required string Name { get; set; }
    public bool IsCompleted { get; set; }
    public DateTime DueDate { get; set; }
}

public static class MyHtml
{

    public static string html(string action, AntiforgeryTokenSet token = null)
    {
        var forgeryInputField = token is null ?
        "" : generateTokenField(token);
        return $"""
    <html><body>
        <form action="{action}" method="POST" enctype="multipart/form-data">
            {forgeryInputField}
            <input type="text" name="name" />
            <input type="date" name="dueDate" />
            <input type="checkbox" name="isCompleted" />
            <input type="submit" />
        </form>
    </body></html>
""";
    }

    public static string generateTokenField(AntiforgeryTokenSet token) => $"""
        <input name="{token.FormFieldName}" 
                      type="hidden" value="{token.RequestToken}" />
    """;
}