Implementation of Digest Authentication for ASP.NET Core (AuthenticationHandler) & ASP.NET (OWIN Middleware).
Supports:
- ASP.NET Core 2.0+ (i.e. NET Standard 2.0+). This includes right the way up to the current .NET 5.0 (
net5.0
) - ASP.NET running on .NET Framework 4.6.1+ (my condolences)
-
Depending on the usage scenario, you have the option of storing the user secrets (passwords) in plaintext or only storing the hashes of the secrets (recommended).
-
If you decide to store just the hashes of user passwords then you will need to pre-compute those using the utility method
DigestAuthentication.ComputeA1Md5Hash
& implementIUsernameHashedSecretProvider
:var hash = DigestAuthentication.ComputeA1Md5Hash("eddie","test-realm", "starwars123"); // 56bde614dc75372a1ef4323904f3beb7 // ... internal class ExampleUsernameHashedSecretProvider : IUsernameHashedSecretProvider { public Task<string> GetA1Md5HashForUsernameAsync(string username, string realm) { if (username == "eddie" && realm == "test-realm") { // The hash value below would have been pre-computed & stored in the database const string hash = "56bde614dc75372a1ef4323904f3beb7"; return Task.FromResult(hash); } // User not found return Task.FromResult<string>(null); } }
-
If, on the other hand you decide to store secrets in plaintext, you will need to provide an implementation of
IUsernameSecretProvider
:internal class ExampleUsernameSecretProvider : IUsernameSecretProvider { public Task<string> GetSecretForUsernameAsync(string username) { if (username == "eddie") { return Task.FromResult("starwars123"); } /// User not found return Task.FromResult<string>(null); } }
In this mode of operation, the library will automatically compute the (user secret) hashes on-the-fly.
-
DigestAuthenticationConfiguration.Create
is used to configure the digest authentication.
ServerNonceSecret
is used when generating the challenges for the client. Keep this safe!Realm
describes (to the user) the computer or system being accessed. This value must match the realm used when pre-computing hashesMaxNonceAgeSeconds
is the number of seconds a given nonce is valid for. After that point, the client will be prompted to reauthenticate
E.g.
DigestAuthenticationConfiguration.Create("SomeVerySecureServerNonceSecret", "SomeDescriptiveRealmName", 30)
- You'll want to install the NuGet package & reference the namespace
FlakeyBit.DigestAuthentication.AspNetCore
.
In your web host startup:
public class Startup
{
public void ConfigureServices(IServiceCollection services) {
// Register our implmentation of IUsernameHashedSecretProvider (or IUsernameSecretProvider if using plaintext)
services.AddScoped<IUsernameHashedSecretProvider, ExampleUsernameHashedSecretProvider>();
services.AddAuthentication("Digest")
.AddDigestAuthentication(DigestAuthenticationConfiguration.Create("SomeVerySecureServerNonceSecret", "SomeDescriptiveRealmName", 30));
// ... Configure more services
}
}
This will add a claim of type DIGEST_AUTHENTICATION_NAME
(value will be the authenticated user name) to the principal on the request context. If you want to simply check for a claim of this type on a controller action, you can use the [Authorize]
attribute as follows:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
[Authorize(AuthenticationSchemes = "Digest")]
public string Get() {
return "Hello Core!";
}
}
- You'll want to reference the NuGet package & namespace
FlakeyBit.DigestAuthentication.AspNetClassic
.
In your web host startup:
public class Startup
{
public void Configuration(IAppBuilder app) {
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {
id = RouteParameter.Optional
});
app.Use<DigestAuthenticationMiddleware>(DigestAuthenticationConfiguration.Create("SomeVerySecureServerNonceSecret", "SomeDescriptiveRealmName", 30), new ExampleUsernameHashedSecretProvider()); // Or an IUsernameSecretProvider for plaintext
app.UseWebApi(config);
}
}
This will add a claim of type DIGEST_AUTHENTICATION_NAME
(value will be the authenticated user name) to the principal on the request context. If you want to simply check for a claim of this type on a controller action, you can use the DigestAuthorize
attribute
public class ValuesController : ApiController
{
[DigestAuthorize]
public string Get() {
return "Protected info!";
}
}
If you want something more sophisticated than that, you'll need to roll your own filter.
For working examples, check out the AspNetClassicApp & AspNetCoreApp projects in the solution.