Custom Authentication in ASP.Net-Core
Creating custom authentication in ASP.NET Core can be done in a variety of ways. If you want to build off existing components (but don't want to use identity), checkout the "Security" category of docs on docs.asp.net. https://docs.asp.net/en/latest/security/index.html
Some articles you might find helpful:
Using Cookie Middleware without ASP.NET Identity
Custom Policy-Based Authorization
And of course, if that fails or docs aren't clear enough, the source code is at
https://github.com/dotnet/aspnetcore/tree/master/src/Security which includes some samples.
Custom authentication with two ADs in ASP.NET Core
To get it working with RazorPages, we need to add the following in Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
// Other statements...
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x => x.LoginPath = "/login");
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other statements...
app.UseAuthentication();
app.UseAuthorization();
}
Then on the PageModel
for the page we'd like to authenticate, we add the [Authorize] attribute to the class.
Then in the LoginModel
PageModel:
public class LoginModel : PageModel
{
[BindProperty]
[DisplayName("Username:")]
[Required]
public string Username { get; set; }
[BindProperty]
[Required]
[DataType(DataType.Password)]
[DisplayName("Password:")]
public string Password { get; set; }
public string ReturnUrl { get; set; }
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
// Check both LDAPs. User must authenticate against one
var isAuthenticated = IsAuthenticated(ConfigurationManager.AppSettings["MyFirstLDAPPath"]) || IsAuthenticated(ConfigurationManager.AppSettings["MySecondLDAPPath"]);
if (isAuthenticated)
{
// Must provide at least the Username claim or it will throw an InvalidOperationException
var identity = new ClaimsIdentity(new List<Claim>{ new Claim(ClaimTypes.Name, Username, ClaimValueTypes.String, "SomeUniqueValue") }, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal,
new AuthenticationProperties());
return LocalRedirect(returnUrl);
}
ViewData["Message"] = "Invalid credentials.";
ReturnUrl = returnUrl;
return Page();
}
// This is a reliable way to check the user's credentials.
// Note that this also considers users with a changed password,
// as some other techniques don't
private bool IsAuthenticated(string ldapPath)
{
try
{
var conn = new LdapConnection(ldapPath);
conn.Credential = new NetworkCredential(Username, Password);
conn.Bind();
return true;
}
catch (Exception)
{
return false;
}
}
}
Then in Login.cshtml
:
<form asp-route-returnurl="@returnUrl" method="post">
@Html.AntiForgeryToken()
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@if (!string.IsNullOrEmpty(ViewData["Message"]?.ToString()))
{
<span class="text-danger">
@ViewData["Message"]
</span>
}
@Html.HiddenFor(x => x.ReturnUrl)
<h3 class="text-center text-info">Login</h3>
<div class="form-group">
<label asp-for="Username" class="text-info"></label>
<input asp-for="Username" class="form-control" autofocus/>
<span asp-validation-for="Username" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="text-info"></label>
<input asp-for="Password" class="form-control"/>
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" name="submit" class="btn btn-info btn-md" value="submit">
</div>
</form>
Hope this helps others.
How do you create a custom AuthorizeAttribute in ASP.NET Core?
The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here. The basic idea behind the new approach is to use the new [Authorize]
attribute to designate a "policy" (e.g. [Authorize( Policy = "YouNeedToBe18ToDoThis")]
where the policy is registered in the application's Startup.cs
to execute some block of code (i.e. ensure the user has an age claim where the age is 18 or older).
The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));
), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.
While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start.
The following is an implementation which uses the IAuthorizationFilter
to provide a simple way to express a claim requirement for a given controller or action:
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] {new Claim(claimType, claimValue) };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly Claim _claim;
public ClaimRequirementFilter(Claim claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
[Route("api/resource")]
public class MyController : Controller
{
[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
[HttpGet]
public IActionResult GetResource()
{
return Ok();
}
}
ASP.NET Core: Custom authentication and authorization
I think you can tell Asp.NET Core to use your custom Identity by applying it on the services middleware registration. Something like
services.AddDefaultIdentity<MyIdentityClass>()
Supposing that MyIdentityClass
extends the IdentityUser
class.
You can find more informations about how to customize the Identity [https://learn.microsoft.com/it-it/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-3.1](here in the Asp.NET Core documentation)
.Net core 3.1 custom authentication
Here is my solution that I am currently using:
This solution works great, and if you have the following in a referenced project, it can easily be reused.
Create a Custom Attribute:
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class MyAuthAttr : ActionFilterAttribute
{
//Add Auth logic here using HttpClient or whatever you use to authenticate.
//You can access your headers through actionContext.HttpContext.Request.Headers
//When completed with your logic, you can continue your controller execution
base.OnActionExecuting(actionContext);
}
This attribute can be applied to your controllers like this:
[MyAuthAttr]
public class MySecureControllerController {...}
This can be applied to the Controller's class as global auth, or any of the endpoints within the controller as specific auth.
So this will work too:
[Route("Do")]
[MyAuthAttr]
public IActionResult DoThaThing(Foo foo) {...}
Related Topics
How to Get Property Change Notifications with Ef 4.X Dbcontext Generator
Removing Dynamically Created Controls in C#
Does Garbage Collector Call Dispose()
How to Suppress a Stylecop Warning
C# Static Member "Inheritance" - Why Does This Exist at All
Converting String Format to Datetime in Mm/Dd/Yyyy
Parsing Performance (If, Tryparse, Try-Catch)
Using SQLdataadapter to Insert a Row
Efficient Cartesian Product Algorithm
Float/Double Precision in Debug/Release Modes
How to Hide an Inherited Property in a Class Without Modifying the Inherited Class (Base Class)
Use Dependency Injection in Static Class
C# Winforms: How to Set Main Function Stathreadattribute
Get the Id of Inserted Row Using C#
How to Set a Conditional Compile Variable
Having the Output of a Console Application in Visual Studio Instead of the Console