Below code return Mono from ReactiveSecurityContextHolder
#Component
public class SomeClass {
public static Mono<String> issuesID() {
return ReactiveSecurityContextHolder.getContext().switchIfEmpty(Mono.empty()).flatMap((securityContext) -> {
Authentication authentication = securityContext.getAuthentication();
Jwt jwt = (Jwt) authentication.getPrincipal();
String issuer = (String) jwt.getClaims().get("some_claim");
log.info("{}",issuer);
return Mono.justOrEmpty(issuer);
}).switchIfEmpty(Mono.empty());
}
}
I need something like this
String mutatedId = PREXIX+SomeClass.issuesID();
If i do followings,
PREXIX+SomeClass.issuesID().block();
PREXIX+SomeClass.issuesID().subscribeOn(Schedulers.elastic()).block();
PREXIX+SomeClass.issuesID().toProcessor().block();
PREXIX+SomeClass.issuesID().toFuture().get();
They all give the same error.
block()/blockFirst()/blockLast() are blocking, which is not supported in thread rector-xxx
I have also tried delaying the Mono but that's also not helping.
I know reactor is a non-blocking framework and blocking is discourages but in my case I can't figure out another way to solve this.
I need to pass the mutatedId to mongoFactory so i can switch databases based on Jwt property per request. I do not want to inject #AuthenticationPrincipal in controller and keep passing it to downward layers and finally make decision at DOA layer.
#Override
public Mono<MongoDatabase> getMongoDatabase(String dbName) throws DataAccessException {
String mutatedId = PREXIX+SomeClass.issuesID();
return super.getMongoDatabase(mutatedId+"#"+dbName);
}
Any suggestion how this can be achieved or is there any better approach.
You use for instance flatMap.
Mono<MongoDatabase> db = issuesID()
.flatMap(id -> getMongoDatabase(id));
I have a controller using a get handler that returns a Future<Content>. I would like to add a header to the response (Cache-Control to be specific). I was thinking that it should be easy but I'm not finding how to do it. Which would be the way to add a header in this case? When we are working with Content instead of Response
To solve the problem you could write your endpoint like this
struct Something: Content {
let text: String
}
router.get("customresponse") { req -> Future<Response> in
return try Something(text: "Hello world").encode(for: req).map { response in
response.http.headers.add(name: .cacheControl, value: "something")
return response
}
}
Mike's answer is spot on... This worked for me. As a further reference, I used a value to extend the cache for 1 day on all public caches.
req.headers.add(name: .cacheControl, value: "public, max-age=86400")
Looking to add the AuthorizeFilterAttribute or AnonymousFilterAttribute to an endpoint in Swashbuckle's implementation of Swagger so I can see which attribute is used on each endpoint in the generated documentation file in a running webapi that ends in /swagger. Is this currenlty possible?
I specifically would like to add a big bold label that says this endpoint is [Anonymous] or that endpoint is using [Authorize] and have them look differently that the summary or remark text.
Also I would like to be able to filter out all the different types of these restriction filter attributes for each endpoint including [NonAction], [Authorize], and [Anonymous] where one of these might be at the top of each controller endpoint. Maybe even eventually add other types of FilterAttributes besides these on each endpoint.
Currently it looks like only the HTTP Methods, the request and response objects can be retrieved in the current implementation so I was not able to find definitive information on this.
Since this is a Swagger implementation do these .NET specific attribute filters not translate to Swashbuckle b/c they only implement what's in the Swagger specification and nothing else?
Finally are their .NET specific extensions to Swashbuckle's implementation that do this?
Thanks!
For the part adding the label to unprotected methods/actions you could use an operation filter like this
public class UnprotectedOperationFilter : IOperationFilter
{
private bool HasAttribute(MethodInfo methodInfo, Type type, bool inherit)
{
// inhertit = true also checks inherited attributes
var actionAttributes = methodInfo.GetCustomAttributes(inherit);
var controllerAttributes = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(inherit);
var actionAndControllerAttributes = actionAttributes.Union(controllerAttributes);
return actionAndControllerAttributes.Any(attr => attr.GetType() == type);
}
public void Apply(Operation operation, OperationFilterContext context)
{
bool hasAuthorizeAttribute = HasAttribute(context.MethodInfo, typeof(AuthorizeAttribute), true);
bool hasAnonymousAttribute = HasAttribute(context.MethodInfo, typeof(AllowAnonymousAttribute), true);
// so far as I understood the action/operation is public/unprotected
// if there is no authorize or an allow anonymous (allow anonymous overrides all authorize)
bool isAuthorized = hasAuthorizeAttribute && !hasAnonymousAttribute;
if (!isAuthorized)
{
operation.Description =
"<p><bold>BIG BOLD LABEL indicating an UPROTECTED PUBLIC method</bold></p>"
+ operation.Description;
}
}
}
and add it with
services.AddSwaggerGen(c => { c.OperationFilter<UnprotectedOperationFilter>();} );
I didn't understand what you mean with filter out different attributes but I hope the code above helps you to check if the attribute is present and do what you desire to do.
How can I return a http status code with sub status like 403.7 from HandleUnauthorizedRequest of inheritor of AuthorizeAttribute? I know how to return a http status code without sub status:
protected override void HandleUnauthorizedRequest(AuthorizationContext ctx)
{
ctx.Result = new HttpStatusCodeResult(403);
}
But I cannot find sub status code in HttpStatusCodeResult constructor.
[UPDATE]
I also try this code, however response start line looks like "HTTP/1.1 403 Forbidden" (not HTTP/1.1 403.7 Forbidden):
protected override void HandleUnauthorizedRequest(AuthorizationContext ctx)
{
ctx.HttpContext.Response.StatusCode = 403;
ctx.HttpContext.Response.SubStatusCode = 7;
ctx.HttpContext.Response.End();
}
While it is possible to set the substatus, that value is never passed to the client and is only useful for things like logging.
Instead you need to get the data to the client in another way. You can either use the response body, or in this case, I'd probably set a custom HTTP header which you will be able to read in your client:
ctx.HttpContext.Response.AddHeader("Status", "SomethingWentWrong");
I want to prevent users submitting forms multiple times in .NET MVC. I've tried several methods using Javascript but have had difficulties getting it to work in all browsers. So, how can I prevent this in my controller? It there some way that multiple submissions can be detected?
Updated answer for ASP.NET Core MVC (.NET Core & .NET 5.0)
Update note: Remember ASP.NET Core is still called "Core" in .NET 5.0.
I'm going to stick to the least-impact use case like before, where you're only adorning those controller actions that you specifically want to prevent duplicate requests on. If you want to have this filter run on every request, or want to use async, there are other options. See this article for more details.
The new form tag helper now automatically includes the AntiForgeryToken so you no longer need to manually add that to your view.
Create a new ActionFilterAttribute like this example. You can do many additional things with this, for example including a time delay check to make sure that even if the user presents two different tokens, they aren't submitting multiple times per minute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext context) {
if (context.HttpContext.Request.HasFormContentType && context.HttpContext.Request.Form.ContainsKey("__RequestVerificationToken")) {
var currentToken = context.HttpContext.Request.Form["__RequestVerificationToken"].ToString();
var lastToken = context.HttpContext.Session.GetString("LastProcessedToken");
if (lastToken == currentToken) {
context.ModelState.AddModelError(string.Empty, "Looks like you accidentally submitted the same form twice.");
}
else {
context.HttpContext.Session.SetString("LastProcessedToken", currentToken);
}
}
}
}
By request, I also wrote an asynchronous version which can be found here.
Here's a contrived usage example of the custom PreventDuplicateRequest attribute.
[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public IActionResult Create(InputModel input) {
if (ModelState.IsValid) {
// ... do something with input
return RedirectToAction(nameof(SomeAction));
}
// ... repopulate bad input model data into a fresh viewmodel
return View(viewModel);
}
A note on testing: simply hitting back in a browser does not use the same AntiForgeryToken. On faster computers where you can't physically double click the button twice, you'll need to use a tool like Fiddler to replay your request with the same token multiple times.
A note on setup: Core MVC does not have sessions enabled by default. You'll need to add the Microsoft.AspNet.Session package to your project, and configure your Startup.cs properly. Please read this article for more details.
Short version of Session setup is:
In Startup.ConfigureServices() you need to add:
services.AddDistributedMemoryCache();
services.AddSession();
In Startup.Configure() you need to add (before app.UseMvc() !!):
app.UseSession();
Original answer for ASP.NET MVC (.NET Framework 4.x)
First, make sure you're using the AntiForgeryToken on your form.
Then you can make a custom ActionFilter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (HttpContext.Current.Request["__RequestVerificationToken"] == null)
return;
var currentToken = HttpContext.Current.Request["__RequestVerificationToken"].ToString();
if (HttpContext.Current.Session["LastProcessedToken"] == null) {
HttpContext.Current.Session["LastProcessedToken"] = currentToken;
return;
}
lock (HttpContext.Current.Session["LastProcessedToken"]) {
var lastToken = HttpContext.Current.Session["LastProcessedToken"].ToString();
if (lastToken == currentToken) {
filterContext.Controller.ViewData.ModelState.AddModelError("", "Looks like you accidentally tried to double post.");
return;
}
HttpContext.Current.Session["LastProcessedToken"] = currentToken;
}
}
}
And on your controller action you just...
[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public ActionResult CreatePost(InputModel input) {
...
}
You'll notice this doesn't prevent the request altogether. Instead it returns an error in the modelstate, so when your action checks if ModelState.IsValid then it will see that it is not, and will return with your normal error handling.
I've tried several methods using Javascript but have had difficulties getting it to work in all browsers
Have you tried using jquery?
$('#myform').submit(function() {
$(this).find(':submit').attr('disabled', 'disabled');
});
This should take care of the browser differences.
Just to complete the answer of #Darin, if you want to handle the client validation (if the form has required fields), you can check if there's input validation error before disabling the submit button :
$('#myform').submit(function () {
if ($(this).find('.input-validation-error').length == 0) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
What if we use $(this).valid()?
$('form').submit(function () {
if ($(this).valid()) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
Strategy
The truth is that you need several lines of attack for this problem:
The Post/Redirect/Get (PRG) pattern is not enough by itself. Still, it should always be used to provide the user with good experiences when using back, refresh, etc.
Using JavaScript to prevent the user from clicking the submit button multiple times is a must because it provides a much less jarring user experience compared to server-side solutions.
Blocking duplicate posts solely on the client side doesn't protect against bad actors and does not help with transient connection problems. (What if your first request made it to the server but the response did not make it back to the client, causing your browser to automatically resend the request?)
I'm not going to cover PRG, but here are my answers for the other two topics. They build upon the other answers here. FYI I'm using .NET Core 3.1.
Client-Side
Assuming you are using jQuery validation, I believe this is the cleanest/most efficient way to prevent your form submit button from being double-clicked. Note that submitHandler is only called after validation has passed, so there is no need to re-validate.
$submitButton = $('#submitButton');
$('#mainForm').data('validator').settings.submitHandler = function (form) {
form.submit();
$submitButton.prop('disabled', true);
};
An alternative to disabling the submit button is to show an overlay in front of the form during submission to 1) block any further interaction with the form and 2) communicate that the page is "doing something." See this article for more detail.
Server-Side
I started off with Jim Yarbro's great answer above, but then I noticed Mark Butler's answer pointing out how Jim's method fails if someone submits forms via multiple browser tabs (because each tab has a different token and posts from different tabs can be interlaced). I confirmed that such a problem really does exist and then decided to upgrade from tracking just the last token to tracking the last x tokens.
To facilitate that, I made a couple of helper classes: one for storing the last x tokens and one for making it easy to store/retrieve objects to/from session storage. The main code now checks that the current token is not found in the token history. Other than that, the code is pretty much the same. I just made some little tweaks to suit my tastes. I included both the regular and asynchronous versions. The full code is below, but these are the critical lines:
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
}
Sadly, the fatal flaw of this approach is that the feedback from the first post (before any duplicates) gets lost. A better (but much more complex) solution would be to store the result of each unique request by GUID, and then handle duplicate requests by not only skipping doing the work again but also returning the same result from the first request, giving the user a seamless experience. This thorough article detailing Air BnB's methods of avoiding duplicate payments will give you an idea of the concepts.
PreventDuplicateFormSubmissionAttribute.cs
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
// This class provides an attribute for controller actions that flags duplicate form submissions
// by adding a model error if the request's verification token has already been seen on a prior
// form submission.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class PreventDuplicateFormSubmissionAttribute: ActionFilterAttribute
{
const string TokenKey = "__RequestVerificationToken";
const string HistoryKey = "RequestVerificationTokenHistory";
const int HistoryCapacity = 5;
const string DuplicateSubmissionErrorMessage =
"Your request was received more than once (either due to a temporary problem with the network or a " +
"double button press). Any submissions after the first one have been rejected, but the status of the " +
"first one is unclear. It may or may not have succeeded. Please check elsewhere to verify that your " +
"request had the intended effect. You may need to resubmit it.";
public override void OnActionExecuting(ActionExecutingContext context)
{
HttpRequest request = context.HttpContext.Request;
if (request.HasFormContentType && request.Form.ContainsKey(TokenKey))
{
string token = request.Form[TokenKey].ToString();
ISession session = context.HttpContext.Session;
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
session.Put(HistoryKey, history);
}
}
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
HttpRequest request = context.HttpContext.Request;
if (request.HasFormContentType && request.Form.ContainsKey(TokenKey))
{
string token = request.Form[TokenKey].ToString();
ISession session = context.HttpContext.Session;
await session.LoadAsync();
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
session.Put(HistoryKey, history);
await session.CommitAsync();
}
await next();
}
}
}
RotatingHistory.cs
using System.Linq;
// This class stores the last x items in an array. Adding a new item overwrites the oldest item
// if there is no more empty space. For the purpose of being JSON-serializable, its data is
// stored via public properties and it has a parameterless constructor.
public class RotatingHistory<T>
{
public T[] Items { get; set; }
public int Index { get; set; }
public RotatingHistory() {}
public RotatingHistory(int capacity)
{
Items = new T[capacity];
}
public void Add(T item)
{
Items[Index] = item;
Index = ++Index % Items.Length;
}
public bool Contains(T item)
{
return Items.Contains(item);
}
}
SessonExtensions.cs
using System.Text.Json;
using Microsoft.AspNetCore.Http;
// This class is for storing (serializable) objects in session storage and retrieving them from it.
public static class SessonExtensions
{
public static void Put<T>(this ISession session, string key, T value) where T : class
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session, string key) where T : class
{
string s = session.GetString(key);
return s == null ? null : JsonSerializer.Deserialize<T>(s);
}
}
You could include a hidden (random or counter) value in the form post, a controller could track these values in an 'open' list or something similar; every time your controller hands out a form it embeds a value, which it tracks allowing one post use of it.
In its self, no, however depending on what the controller is actually doing, you should be able to work out a way.
Is a record being created in the database that you can check for to see if they've already submitted the form?
Just add this code at the end of your page. I am using "jquery-3.3.1.min.js" and "bootstrap 4.3.1"
<script type="text/javascript">
$('form').submit(function () {
if ($(this).valid()) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
</script>
Use the Post/Redirect/Get design pattern.
PS:
It looks to me that the answer by Jim Yarbro could have a fundamental flaw in that the __RequestVerificationToken stored in the HttpContext.Current.Session["LastProcessedToken"] will be replaced when a second form is submitted (from say another browser window). At this point, it is possible to re-submit the first form without it being recognized as a duplicate submission. For the proposed model to work, wouldn’t a history of __RequestVerificationToken be required? This doesn't seem feasible.
Dont reinvent the wheel :)
Use the Post/Redirect/Get design pattern.
Here you can find a question and an answer giving some suggestions on how to implement it in ASP.NET MVC.
You can also pass some sort of token in a hidden field and validate this in the controller.
Or you work with redirects after submitting values. But this get's difficult if you take heavily advantage of ajax.
This works on every browser
document.onkeydown = function () {
switch (event.keyCode) {
case 116: //F5 button
event.returnValue = false;
event.keyCode = 0;
return false;
case 82: //R button
if (event.ctrlKey) {
event.returnValue = false;
event.keyCode = 0;
return false;
}
}
}
You can do this by creating some sort of static entry flag that is user specific, or specific to whatever way you want to protect the resource. I use a ConcurrentDictionary to track entrance. The key is basically the name of the resource I'm protecting combined with the User ID. The trick is figuring out how to block the request when you know it's currently processing.
public async Task<ActionResult> SlowAction()
{
if(!CanEnterResource(nameof(SlowAction)) return new HttpStatusCodeResult(204);
try
{
// Do slow process
return new SlowProcessActionResult();
}
finally
{
ExitedResource(nameof(SlowAction));
}
}
Returning a 204 is a response to the double-click request that will do nothing on the browser side. When the slow process is done, the browser will receive the correct response for the original request and act accordingly.
Use this simple jquery input field and will work awesomely even if you have multiple submit buttons in a single form.
$('input[type=submit]').click(function () {
var clickedBtn = $(this)
setTimeout(function () {
clickedBtn.attr('disabled', 'disabled');
}, 1);
});