ASP.NET MVC 3 app, BCrypt.CheckPassword failing - asp.net-mvc

I'm working on implementing security in an ASP.NET MVC 3 application, and am using the BCrypt implementation found here to handle encryption and verification of passwords. The user registration screen encrypts the password the user provides just fine, and the hashed password gets saved to the database. I'm having a problem with password verification on the login page though, and I can't seem to figure out why.
My registration controller action contains the following:
[HttpPost]
[RequireHttps]
public ActionResult Register(Registration registration)
{
// Validation logic...
try
{
var user = new User
{
Username = registration.Username,
Password = Password.Hash(HttpUtility.HtmlDecode(registration.Password)),
EmailAddress = registration.EmailAddress,
FirstName = registration.FirstName,
MiddleInitial = registration.MiddleInitial,
LastName = registration.LastName,
DateCreated = DateTime.Now,
DateModified = DateTime.Now,
LastLogin = DateTime.Now
};
var userId = _repository.CreateUser(user);
}
catch (Exception ex)
{
ModelState.AddModelError("User", "Error creating user, please try again.");
return View(registration);
}
// Do some other stuff...
}
This is Password.Hash:
public static string Hash(string password)
{
return BCrypt.HashPassword(password, BCrypt.GenerateSalt(12));
}
This is how I'm handling login:
[HttpPost]
[RequireHttps]
public ActionResult Login(Credentials login)
{
// Validation logic...
var authorized = _repository.CredentialsAreValid(HttpUtility.HtmlDecode(login.username), login.password);
if (authorized)
{
// log the user in...
}
else
{
ModelState.AddModelError("AuthFail", "Authentication failed, please try again");
return View(login);
}
}
CredentialsAreValid wraps the call to BCrypt.CheckPassword:
public bool CredentialsAreValid(string username, string password)
{
var user = GetUser(username);
if (user == null)
return false;
return Password.Compare(password, user.Password);
}
Password.Compare:
public static bool Compare(string password, string hash)
{
return BCrypt.CheckPassword(password, hash);
}
And finally, this is what BCrypt.CheckPassword is doing:
public static bool CheckPassword(string plaintext, string hashed)
{
return StringComparer.Ordinal.Compare(hashed, HashPassword(plaintext, hashed)) == 0;
}
So, yeah...I dunno what's going on, but what I do know, is that my boolean authorized variable in my Login controller action always returns false for some reason.
I've used this exact same BCrypt class on at least a couple of other projects in the past, and never had any problems with it at all. Is ASP.NET MVC 3 doing some weird, different encoding to posted data that I'm missing or need to handle differently or something? Either that, or is SQL CE 4 doing it (that's the datastore I'm currently using)? Everything seems to be in order in my code from what I can tell, but for some reason, password checking is failing every time. Anyone have any ideas?
Thanks.
UPDATE: Here's the code comments included with the BCrypt class with examples of how it's used and works.
/// <summary>BCrypt implements OpenBSD-style Blowfish password hashing
/// using the scheme described in "A Future-Adaptable Password Scheme"
/// by Niels Provos and David Mazieres.</summary>
/// <remarks>
/// <para>This password hashing system tries to thwart offline
/// password cracking using a computationally-intensive hashing
/// algorithm, based on Bruce Schneier's Blowfish cipher. The work
/// factor of the algorithm is parametized, so it can be increased as
/// computers get faster.</para>
/// <para>To hash a password for the first time, call the
/// <c>HashPassword</c> method with a random salt, like this:</para>
/// <code>
/// string hashed = BCrypt.HashPassword(plainPassword, BCrypt.GenerateSalt());
/// </code>
/// <para>To check whether a plaintext password matches one that has
/// been hashed previously, use the <c>CheckPassword</c> method:</para>
/// <code>
/// if (BCrypt.CheckPassword(candidatePassword, storedHash)) {
/// Console.WriteLine("It matches");
/// } else {
/// Console.WriteLine("It does not match");
/// }
/// </code>
/// <para>The <c>GenerateSalt</c> method takes an optional parameter
/// (logRounds) that determines the computational complexity of the
/// hashing:</para>
/// <code>
/// string strongSalt = BCrypt.GenerateSalt(10);
/// string strongerSalt = BCrypt.GenerateSalt(12);
/// </code>
/// <para>
/// The amount of work increases exponentially (2**log_rounds), so
/// each increment is twice as much work. The default log_rounds is
/// 10, and the valid range is 4 to 31.
/// </para>
/// </remarks>

Forgive me if I'm missing something, but looking at your hash and your model you don't seem to store the salt anywhere, instead you use a new salt each time.
So when the password is set you must store both the hash and the salt; when you want to check an entered password you retrieve the salt, compute the hash using it, then compare against the stored one.

I had the same problem. BCryptHelper.CheckPassword always returns false
I found that the the hashed string was stored in the db as a nchar(). This caused the check to always fail.
I changed this to char() and it works.

HttpUtility.HtmlDecode() is used when the user is created, before the password is originally hashed:
Password = Password.Hash(HttpUtility.HtmlDecode(registration.Password)),
However, HttpUtility.HtmlDecode() is not used when later when comparing password to hash, in
var authorized = _repository.CredentialsAreValid(HttpUtility.HtmlDecode(login.username), login.password);
Perhaps a slight change to:
var authorized = _repository.CredentialsAreValid(HttpUtility.HtmlDecode(login.username), HttpUtility.HtmlDecode(login.password));
I realize this is an older question but I'm contemplating using BCrypt and this question raised a potential flag for me so I'm interested in knowing if this resolves this issue. I apologize, I'm not in a position at the moment to verify my answer, but I hope it helps.

Related

How to decrypt password in MVC5?

I am using MVC5, i know that if a user forgets his password, then MVC provides the feature of forgot password and reset password.
My client server is disconnected from internet or mailing, it is behind the firewalls, so i cannot use forgot password, as it might generate a link to reset password, but cannot mail it to the user to facilitate the password reset.
Please suggest if there is any way to decrypt the password(to let user know if he forgets his password) like how it was available in asp.net membership by simply using the GetPassword method of the membership classes.
Thank you
As far I know there is no easy way to do this in MVC5, because Identity (next gen of Membership) is using hash of password rather then encrypted password.
Password is hashed and stored in db as a hash - generally it's one-way operation (it's mean that there is no easy way to get password form hash).
Little bit more about what is hashing and salting you can read here:
How to securely store passwords and beat the hackers
How does hashing work?
This step to Ecrypt and decrypt password in asp.net mvc5.
create class name Hashing, paste this code
private static string GetRandomSalt()
{
return BCrypt.Net.BCrypt.GenerateSalt(12);
}
public static string HashPassword(string password)
{
return BCrypt.Net.BCrypt.HashPassword(password, GetRandomSalt());
}
public static bool ValidatePassword(string password, string correctHash)
{
return BCrypt.Net.BCrypt.Verify(password, correctHash);
}
Create controller login you past this code
using WebBcryptMVC.Models; //
using WebBcryptMVC.Util; // call folder name of Hashing class
namespace WebBcryptMVC.Controllers
{
public class LoginController : Controller
{
private DBLoginEntities db = new DBLoginEntities();
public ActionResult frmLogin()
{
return View("frmLogin", new tblLogin());
}
[HttpPost]
public ActionResult frmLogin(tblLogin account)
{
var currentAccount = db.tblLogins.First(a => a.UserName.Equals(account.UserName));
if ((currentAccount != null))
{
if (Hashing.ValidatePassword(account.Password, currentAccount.Password))
{
Session.Add("UserName", account.UserName);
//return View("~/Views/Home/frmHome.cshtml");
return RedirectToAction("frmHome", "Home");
}
else
{
ViewBag.error = "Invalid";
return View("frmLogin");
}
}
else
{
ViewBag.error = "Invalid";
return View("frmLogin");
}
}

What is the burden of User.Identity.GetUserId()?

In my ASP.NET MVC applications I use User.Identity.GetUserId() abundantly. However, I wonder if this has severe performance penalties.
Alternatively, I believe I can do this: In a View, I can assign the current user's id to a hidden field in the first page load. Then, when making AJAX calls, I can pass the hidden field value to controllers' actions. This way, I would not need to use User.Identity.GetUserId() method to retrieve the userid of the current user.
I wonder if anyone has any ideas on this?
Take a look at the source for GetUserId extension method:
/// <summary>
/// Return the user id using the UserIdClaimType
/// </summary>
/// <param name="identity"></param>
/// <returns></returns>
public static string GetUserId(this IIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
var ci = identity as ClaimsIdentity;
if (ci != null)
{
return ci.FindFirstValue(ClaimTypes.NameIdentifier);
}
return null;
}
/// <summary>
/// Return the claim value for the first claim with the specified type if it exists, null otherwise
/// </summary>
/// <param name="identity"></param>
/// <param name="claimType"></param>
/// <returns></returns>
public static string FindFirstValue(this ClaimsIdentity identity, string claimType)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
var claim = identity.FindFirst(claimType);
return claim != null ? claim.Value : null;
}
Every time you call that extension method it searches the identity for the ClaimTypes.NameIdentifier claim.
The performance impact is not that substantial (IMO) but leaking user information in hidden (there not actually hidden if one can see them with one click of view source) is not a good idea.
If you are concerned about calling it multiple times and need it in multiple locations through out a request then you can have it lazy loaded behind a property in your controller or a base controller.
private string userId
public string UserId {
get {
if(userid == null) {
userid = User.Identity.GetUserId();
}
return userid;
}
}
You could also create a service to encapsulate that information.

Displaying the fullname (firstname, lastname) of the logged in user

I work on an ASP.NET MVC4 solution. When the user is logged in, I would like to display his fullname (not the username provided in the login form). His fullname (firstname + lastname actually stored in the user table in my database) should be displayed in the top right corner.
For better performance, I don't want to query the database each time a request is done.
How to proceed?
Keeping the user information (firstname, lastname, ...) in a cookie?
Keeping the user information is a session variable for all the lifecycle of the application?
Keeping the user information in a 'Profile' like explained here: How to assign Profile values? (*)
Something else?
(*) I think this solution a little complex for the use I have.
Thanks.
I would use a cookie. It doesn't hog up any memory on your machine like Session, and it doesn't hit the database like Profile would. Just remember to delete the cookie when the user signs off.
Note that the Profile would hit the database server each time you make a request. As far as I know, Profile data is not cached anywhere on the web server (unless you have a custom profile provider).
Another reason why I like cookie: if you ever want to store any additional user information for fast access, like UserPrimaryKey, or any special user preferences, you can just store them as JSON in the cookie. Here is an example:
Another note: the code below uses Newtonsoft.Json (the JsonConvert lines). It should come out of the box in an MVC4 project, but for an MVC3 project, you can just add it via nuget.
public class UserCacheModel
{
public string FullName { get; set; }
public string Preference1 { get; set; }
public int Preference2 { get; set; }
public bool PreferenceN { get; set; }
}
public static class UserCacheExtensions
{
private const string CookieName = "UserCache";
// put the info in a cookie
public static void UserCache(this HttpResponseBase response, UserCacheModel info)
{
// serialize model to json
var json = JsonConvert.SerializeObject(info);
// create a cookie
var cookie = new HttpCookie(CookieName, json)
{
// I **think** if you omit this property, it will tell the browser
// to delete the cookie when the user closes the browser window
Expires = DateTime.UtcNow.AddDays(60),
};
// write the cookie
response.SetCookie(cookie);
}
// get the info from cookie
public static UserCacheModel UserCache(this HttpRequestBase request)
{
// default user cache is empty
var json = "{}";
// try to get user cache json from cookie
var cookie = request.Cookies.Get(CookieName);
if (cookie != null)
json = cookie.Value ?? json;
// deserialize & return the user cache info from json
var userCache = JsonConvert.DeserializeObject<UserCacheModel>(json);
return userCache;
}
}
With this, you can read / write the cookie info from a controller like this:
// set the info
public ActionResult MyAction()
{
var fullName = MethodToGetFullName();
var userCache = new UserCache { FullName = fullName };
Response.UserCache(userCache);
return Redirect... // you must redirect to set the cookie
}
// get the info
public ActionResult MyOtherAction()
{
var userCache = Request.UserCache();
ViewBag.FullName = userCache.FullName;
return View();
}

Which part of an application should be responsible for hashing a user's password?

I'm writing an ASP.NET MVC application which will provide user registration functionality but i am not sure which part of the application (e.g. User Domain model object, Controller, ViewModelMappers) should be responsible for hashing the user's password. I have a registration page that uses a strongly typed ViewModel and a Register action in my UserController as shown:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Register(UserRegisterViewModel userRegisterViewModel)
{
var user = userViewModelMapper.Map(userRegisterViewModel);
INotification validationResult = user.ValidateForRegistration(userRepository);
if (!validationResult.HasErrors)
{
user.HashPassword();
userRepository.AddOrUpdate(user); // i'm using NHibernate
return View("RegistrationAcknowledgement");
}
foreach (IError error in validationResult.Errors)
ModelState.AddModelError(error.Property, error.Message);
ViewData["country"] = new SelectList(countryRepository.GetAll(), "Code", "Name", userRegisterViewModel.Country);
return View("RegistrationForm", userRegisterViewModel);
}
User objects are composed in part by LoginDetail objects as shown and to avoid exposing the internals of the User object beyond what is absolutely required the Password Property is read-only. So i cannot for example do user.LoginDetails.Password = hashedandSaltedPassword;
namespace XXXX.Core.Model
{
public class User
{
private LoginDetails loginDetails;
public virtual LoginDetails LoginDetails
{
get { return loginDetails; }
private set { loginDetails = value; }
}
public virtual void AssignLoginDetails(LoginDetails loginDetails)
{
this.loginDetails = loginDetails;
}
public virtual void HashPassword()
{
IHashGenerator hashGenerator = new HashGenerator(new SaltGenerator());
IHashResult hashResult = hashGenerator.GenerateHash(loginDetails.Password, HashAlgoritm.SHA512);
loginDetails.Password = String.Concat(hashResult.HashValue, hashResult.Salt);
}
}
}
namespace XXXX.Core.Model
{
public class LoginDetails
{
private string username;
private string password;
private string confirmPassword;
private string passwordReminder;
private bool changePassword;
// Properties
#region Constructors
...
public LoginDetails(string username, string password, string confirmPassword, string passwordReminder, bool changePassword)
{
this.username = username;
this.password = password;
this.confirmPassword = confirmPassword;
this.passwordReminder = passwordReminder;
this.changePassword = changePassword;
}
}
}
Currently the responsibility for hashing the password is owned the User (by means of the HashPassword method) but
1. Is this a correct responsibility for the User to have (within the context of DDD and Single Responsibility principle)
2. If not, where should this operation reside?
3. If so, should it be called from the controller as i am doing?
Thanks
Without reading your code, I would argue hashing the password should be done in the model so it can be reused outside of the MVC framework. This tends to be true in all MVC frameworks that are implemented in languages general enough to be useful outside of the web.
Let's take a step back and look at the broader picture: when do we want to take a password in clear and hash it?
when the user is creating or
changing their password, and we need
to store it
when the user is logging in, and we
need to compare the entered
password with the the stored one
Currently your implementation addresses only the first instance. So you need a method which accepts a clear password and returns a hashed one.
As for where that method should go ...
The Single Responsibility Principle does not mean that a class does literally one thing. It means that the class handles only things which are clearly within its remit.
So, consider is the relationship between User and hashed password. Can you have a User wthout a hashed password? Will you ever want to work with a hashed password without its User? Do you have other objects which have a hashed password besides User? If the answer to those questions is "No" then I would argue that the password hashing method clearly belongs to the User class, and indeed increases its cohesiveness.

Best way to implement request throttling in ASP.NET MVC?

We're experimenting with various ways to throttle user actions in a given time period:
Limit question/answer posts
Limit edits
Limit feed retrievals
For the time being, we're using the Cache to simply insert a record of user activity - if that record exists if/when the user does the same activity, we throttle.
Using the Cache automatically gives us stale data cleaning and sliding activity windows of users, but how it will scale could be a problem.
What are some other ways of ensuring that requests/user actions can be effectively throttled (emphasis on stability)?
Here's a generic version of what we've been using on Stack Overflow for the past year:
/// <summary>
/// Decorates any MVC route that needs to have client requests limited by time.
/// </summary>
/// <remarks>
/// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ThrottleAttribute : ActionFilterAttribute
{
/// <summary>
/// A unique name for this Throttle.
/// </summary>
/// <remarks>
/// We'll be inserting a Cache record based on this name and client IP, e.g. "Name-192.168.0.1"
/// </remarks>
public string Name { get; set; }
/// <summary>
/// The number of seconds clients must wait before executing this decorated route again.
/// </summary>
public int Seconds { get; set; }
/// <summary>
/// A text message that will be sent to the client upon throttling. You can include the token {n} to
/// show this.Seconds in the message, e.g. "Wait {n} seconds before trying again".
/// </summary>
public string Message { get; set; }
public override void OnActionExecuting(ActionExecutingContext c)
{
var key = string.Concat(Name, "-", c.HttpContext.Request.UserHostAddress);
var allowExecute = false;
if (HttpRuntime.Cache[key] == null)
{
HttpRuntime.Cache.Add(key,
true, // is this the smallest data we can have?
null, // no dependencies
DateTime.Now.AddSeconds(Seconds), // absolute expiration
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null); // no callback
allowExecute = true;
}
if (!allowExecute)
{
if (String.IsNullOrEmpty(Message))
Message = "You may only perform this action every {n} seconds.";
c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
// see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
}
}
}
Sample usage:
[Throttle(Name="TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]
public ActionResult TestThrottle()
{
return Content("TestThrottle executed");
}
The ASP.NET Cache works like a champ here - by using it, you get automatic clean-up of your throttle entries. And with our growing traffic, we're not seeing that this is an issue on the server.
Feel free to give feedback on this method; when we make Stack Overflow better, you get your Ewok fix even faster :)
Microsoft has a new extension for IIS 7 called Dynamic IP Restrictions Extension for IIS 7.0 - Beta.
"The Dynamic IP Restrictions for IIS 7.0 is a module that provides protection against denial of service and brute force attacks on web server and web sites. Such protection is provided by temporarily blocking IP addresses of the HTTP clients who make unusually high number of concurrent requests or who make large number of requests over small period of time."
http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/
Example:
If you set the criteria to block after X requests in Y milliseconds or X concurrent connections in Y milliseconds the IP address will be blocked for Y milliseconds then requests will be permitted again.
We use the technique borrowed from this URL http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx, not for throttling, but for a poor man's Denial Of Service (D.O.S). This is also cache-based, and may be similar to what you are doing. Are you throttling to prevent D.O.S. attacks? Routers can certainly be used to reduce D.O.S; do you think a router could handle the throttling you need?
It took me some time to work out an equivalent for .NET 5+ (formerly .NET Core), so here's a starting point.
The old way of caching has gone and been replaced by Microsoft.Extensions.Caching.Memory with IMemoryCache.
I separated it out a bit more, so here's what you need...
The Cache Management Class
I've added the whole thing here, so you can see the using statements.
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using System;
using System.Threading;
namespace MyWebApplication
{
public interface IThrottleCache
{
bool AddToCache(string key, int expriryTimeInSeconds);
bool AddToCache<T>(string key, T value, int expriryTimeInSeconds);
T GetFromCache<T>(string key);
bool IsInCache(string key);
}
/// <summary>
/// A caching class, based on the docs
/// https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-6.0
/// Uses the recommended library "Microsoft.Extensions.Caching.Memory"
/// </summary>
public class ThrottleCache : IThrottleCache
{
private IMemoryCache _memoryCache;
public ThrottleCache(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public bool AddToCache(string key, int expriryTimeInSeconds)
{
bool isSuccess = false; // Only a success if a new value gets added.
if (!IsInCache(key))
{
var cancellationTokenSource = new CancellationTokenSource(
TimeSpan.FromSeconds(expriryTimeInSeconds));
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSize(1)
.AddExpirationToken(
new CancellationChangeToken(cancellationTokenSource.Token));
_memoryCache.Set(key, DateTime.Now, cacheEntryOptions);
isSuccess = true;
}
return isSuccess;
}
public bool AddToCache<T>(string key, T value, int expriryTimeInSeconds)
{
bool isSuccess = false;
if (!IsInCache(key))
{
var cancellationTokenSource = new CancellationTokenSource(
TimeSpan.FromSeconds(expriryTimeInSeconds));
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(DateTimeOffset.Now.AddSeconds(expriryTimeInSeconds))
.SetSize(1)
.AddExpirationToken(
new CancellationChangeToken(cancellationTokenSource.Token));
_memoryCache.Set<T>(key, value, cacheEntryOptions);
isSuccess = true;
}
return isSuccess;
}
public T GetFromCache<T>(string key)
{
return _memoryCache.Get<T>(key);
}
public bool IsInCache(string key)
{
var item = _memoryCache.Get(key);
return item != null;
}
}
}
The attribute itself
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Net;
namespace MyWebApplication
{
/// <summary>
/// Decorates any MVC route that needs to have client requests limited by time.
/// Based on how they throttle at stack overflow (updated for .NET5+)
/// https://stackoverflow.com/questions/33969/best-way-to-implement-request-throttling-in-asp-net-mvc/1318059#1318059
/// </summary>
/// <remarks>
/// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ThrottleByIPAddressAttribute : ActionFilterAttribute
{
/// <summary>
/// The caching class (which will be instantiated as a singleton)
/// </summary>
private IThrottleCache _throttleCache;
/// <summary>
/// A unique name for this Throttle.
/// </summary>
/// <remarks>
/// We'll be inserting a Cache record based on this name and client IP, e.g. "Name-192.168.0.1"
/// </remarks>
public string Name { get; set; }
/// <summary>
/// The number of seconds clients must wait before executing this decorated route again.
/// </summary>
public int Seconds { get; set; }
/// <summary>
/// A text message that will be sent to the client upon throttling. You can include the token {n} to
/// show this.Seconds in the message, e.g. "Wait {n} seconds before trying again".
/// </summary>
public string Message { get; set; } = "You may only perform this action every {n} seconds.";
public override void OnActionExecuting(ActionExecutingContext c)
{
if(_throttleCache == null)
{
var cache = c.HttpContext.RequestServices.GetService(typeof(IThrottleCache));
_throttleCache = (IThrottleCache)cache;
}
var key = string.Concat(Name, "-", c.HttpContext.Request.HttpContext.Connection.RemoteIpAddress);
var allowExecute = _throttleCache.AddToCache(key, Seconds);
if (!allowExecute)
{
if (String.IsNullOrEmpty(Message))
Message = "You may only perform this action every {n} seconds.";
c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
// see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
}
}
}
}
Startup.cs or Program.cs - Register the services with DI
This example uses Startup.cs/ConfigureServices - Put the code somewhere after AddControllersWithViews).
For a project created in .NET6+ I think you'd add the equivalent between builder.Services.AddRazorPages(); and var app = builder.Build(); in program.cs. services would be builder.Services.
If you don't get the placement of this code right, the cache will be empty every time you check it.
// The cache for throttling must be a singleton and requires IMemoryCache to be set up.
// Place it after AddControllersWithViews or AddRazorPages as they build a cache themselves
// Need this for IThrottleCache to work.
services.AddMemoryCache(_ => new MemoryCacheOptions
{
SizeLimit = 1024, /* TODO: CHECK THIS IS THIS THE RIGHT SIZE FOR YOU! */
CompactionPercentage = .3,
ExpirationScanFrequency = TimeSpan.FromSeconds(30),
});
services.AddSingleton<IThrottleCache, ThrottleCache>();
Example Usage
[HttpGet, Route("GetTest")]
[ThrottleByIPAddress(Name = "MyControllerGetTest", Seconds = 5)]
public async Task<ActionResult<string>> GetTest()
{
return "Hello world";
}
To help understand caching in .NET 5+, I've also made a caching console demo.
Since the highly voted answers to this question are too old, I am sharing the latest solution which worked for me.
I tried using the Dynamic IP restrictions as given in an answer on this page but when I tried to use that extension, I found that this extension has been discontinued by Microsoft and on the download page they have clearly written the below message.
Microsoft has discontinued the Dynamic IP Restrictions extension and this download is no longer available.
So I researched further and found that the Dynamic IP Restrictions is now by default included in IIS 8.0 and above. The below information is fetched from the Microsoft Dynamic IP Restrictions page.
In IIS 8.0, Microsoft has expanded the built-in functionality to include several new features:
Dynamic IP address filtering, which allows administrators to
configure their server to block access for IP addresses that exceed
the specified number of requests.
The IP address filtering features now allow administrators to specify
the behavior when IIS blocks an IP address, so requests from
malicious clients can be aborted by the server instead of returning
HTTP 403.6 responses to the client.
IP filtering now feature a proxy mode, which allows IP addresses to
be blocked not only by the client IP that is seen by IIS but also by
the values that are received in the x-forwarded-for HTTP header
For step by step instructions to implement Dynamic IP Restrictions, please visit the below link:
https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-dynamic-ip-address-restrictions
I hope it helps someone stuck in a similar problem.
Created ThrottlingTroll - my take on throttling/rate limiting in ASP.NET Core.
It is similar to Stefan Prodan's AspNetCoreRateLimit and ASP.NET 7's Rate Limiting Middleware, but has advantages:
Both ingress and egress throttling (egress means that your specially configured HttpClient won't make more than N requests per second and will instead produce 429 status code by itself).
Distributed rate counter stores (including, but not limited to Redis).
Dynamic (re)configuration - allows to adjust limits without restarting the service.
Propagating 429 statuses from egress to ingress.
Check out more in the repo.

Resources