Custom Authentication in ASP.NET-Core

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



Leave a reply



Submit