ASP.NET Core Identity is popular choice when web application needs authentication. It supports local accounts with username and password but also social ID-s like Facebook, Twitter, Microsoft Account etc. But what if ASP.NET Core Identity is too much for us and we need something smaller? What if requirements make it impossible to use it? Here’s my lightweight solution for custom authentication in ASP.NET Core.
We don’t have to use ASP.NET Core Identity always when we need authentication. I have specially interesting case I’m working on right now.
I’m building a site where users authenticate using Estonian ID-card and it’s mobile counterpart mobile-ID. In both cases users is identified by official person code. Users can also use authentication services by local banks. Protocol is different but users are again identified by official person code. There will be no username-password or social media authentication.
In my case I don’t need ASP.NET Core Identity as it’s too much and probably there are some security requirements that wipe classic username and password authentication off from table.
Configuring authentication
After some research it turned out that it’s actually very easy to go with cookie authentication and custom page where I implement support for those exotic authentication mechanisms.
First we have to tell ASP.NET Core that we need authentication. I’m going with cookie authentication as there’s no ping-pong between my site and external authentication services later. Let’s head to ConfigureServices() method of Startup class and enable authentication.
public void ConfigureServices(IServiceCollection services)
{
// Enable cookie authentication
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
services.AddHttpContextAccessor();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
In Configure() method of Startup class we need to add authentication to request processing pipeline. The line after comment does the job.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
// Add authentication to request pipeline
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
No we have done smallest part of work – ASP.NET Core is configured to use cookie authentication.
Implementing AccountController
Where is user redirected when authentication is needed? We didn’t say anything about it Startup class. If we don’t specify anything then ASP.NET Core expects AccountController with AccessDenied and Login actions. It’s bare minimum for my case. As users must be able to log out I added also Logout() action.
Here’s my account controller. Login() action is called with SSN parameter only by JavaScript that performs actual authentication. There’s always SSN when this method is called (of course, I will add more sanity checks later). Notice how I build up claims identity claim by claim.
public class AccountController : BaseController
{
private readonly IUserService _userService;
public AccountController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(string ssn)
{
var user = await _userService.GetAllowedUser(ssn);
if (user == null)
{
ModelState.AddModelError("", "User not found");
return View();
}
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Ssn));
identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName));
identity.AddClaim(new Claim(ClaimTypes.Surname, user.LastName));
foreach (var role in user.Roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role.Role));
}
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return RedirectToAction("Index","Home");
}
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync();
return RedirectToAction(nameof(Login));
}
public IActionResult AccessDenied()
{
return View();
}
}
And this is it. I have now cookie-based custom authentication in my web application.
To try things out I run my web application, log in and check what’s inside claims collection of current user. All claims I expected are there.
Wrapping up
ASP.NET Core is great on providing the base for basic, simple and lightweight solutions that doesn’t grow monsters over night. For authentication we can go with ASP.NET Core Identity but if it’s too much or not legally possible then it’s so-so easy to build our own custom cookie-based authentication. All we did was writing few lines of code to Startup class. On controllers side we needed just a simple AccountController where we implemented few actions for logging in, logging out and displaying access denied message.
View Comments (9)
I wonder what the form or service looks like that allows the user to type in their SSN?
Can you do a text message solution?
Also, I like Auth0
Thanks
SSN is used by Estonian mobile-ID. On auth form you type in your SSN and mobile number and click login button. Then you will get numeric code to your device and using SIM application in your phone you enter your PIN. After this you will be authenticated. It's not just a plain SMS but SIM application is actually encrypting the message using your PIN and private key. It's validated centrally and if everything is okay then you get OK response back from mobile-ID service.
I implemented your code and it went almost all right. Only in the public AccountController (IUserService userService) constructor appears the message that IUserService could be not found. What should I do?
I'm not using ASP.NET Core Identity as I don't need to support multiple providers or authentication through social sites. If you want to use ASP.NET Core Identity then you have to configure it in application startup.
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.0
Missed code:
var cookiePolicyOptions = new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.Strict,
};
app.UseCookiePolicy(cookiePolicyOptions);
Thanks for your post! It's save me a lot of time.
I follow your post and it run ok. But I want to call jquery ajax with it. Can you share me a sample about call ajax with token sample?
Great article. It works, I want to use only password to authenticate.
Nice post.
I didn't understand your response to the question about IUserService. Yours is the second code sample I've found where at least one part of it requires a class or interface not referenced... anywhere I can find.
My use case is the "simple" one of authenticating to an existing database table being used by an existing, production (but legacy software, ASP.NET WebForms), for which we may not have the source code.
All I need to do is to take the user and password from the ASP.NET Core login page and access our existing database for the Authentication part. Should be easy, but I've already spent several hours on it with no results. Any insights appreciated.