I am working on a web app that needs to integrate with an existing user database. I would still like to use the [Authorize] attributes, but I don't want to use the Identity framework. If I did want to use the Identity framework I would add something like this in the startup.cs file:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireNonLetterOrDigit = false;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
I'm assuming I have to add something else there, and then create some kind of class that implements a specific interface? Can somebody point me in the right direction? I'm using RC1 of of asp.net 5 right now.
From what I learned after several days of research,
Here is the Guide for ASP .Net Core MVC 2.x Custom User Authentication
In Startup.cs :
Add below lines to ConfigureServices method :
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CookieAuthenticationDefaults.AuthenticationScheme
).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = "/Account/Login";
options.LogoutPath = "/Account/Logout";
});
services.AddMvc();
// authentication
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
services.AddTransient(
m => new UserManager(
Configuration
.GetValue<string>(
DEFAULT_CONNECTIONSTRING //this is a string constant
)
)
);
services.AddDistributedMemoryCache();
}
keep in mind that in above code we said that if any unauthenticated user requests an action which is annotated with [Authorize] , they well force redirect to /Account/Login url.
Add below lines to Configure method :
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler(ERROR_URL);
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: DEFAULT_ROUTING);
});
}
Create your UserManager class that will also manage login and logout. it should look like below snippet (note that i'm using dapper):
public class UserManager
{
string _connectionString;
public UserManager(string connectionString)
{
_connectionString = connectionString;
}
public async void SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)
{
using (var con = new SqlConnection(_connectionString))
{
var queryString = "sp_user_login";
var dbUserData = con.Query<UserDbModel>(
queryString,
new
{
UserEmail = user.UserEmail,
UserPassword = user.UserPassword,
UserCellphone = user.UserCellphone
},
commandType: CommandType.StoredProcedure
).FirstOrDefault();
ClaimsIdentity identity = new ClaimsIdentity(this.GetUserClaims(dbUserData), CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
}
}
public async void SignOut(HttpContext httpContext)
{
await httpContext.SignOutAsync();
}
private IEnumerable<Claim> GetUserClaims(UserDbModel user)
{
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id().ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserFirstName));
claims.Add(new Claim(ClaimTypes.Email, user.UserEmail));
claims.AddRange(this.GetUserRoleClaims(user));
return claims;
}
private IEnumerable<Claim> GetUserRoleClaims(UserDbModel user)
{
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id().ToString()));
claims.Add(new Claim(ClaimTypes.Role, user.UserPermissionType.ToString()));
return claims;
}
}
Then maybe you have an AccountController which has a Login Action that should look like below :
public class AccountController : Controller
{
UserManager _userManager;
public AccountController(UserManager userManager)
{
_userManager = userManager;
}
[HttpPost]
public IActionResult LogIn(LogInViewModel form)
{
if (!ModelState.IsValid)
return View(form);
try
{
//authenticate
var user = new UserDbModel()
{
UserEmail = form.Email,
UserCellphone = form.Cellphone,
UserPassword = form.Password
};
_userManager.SignIn(this.HttpContext, user);
return RedirectToAction("Search", "Home", null);
}
catch (Exception ex)
{
ModelState.AddModelError("summary", ex.Message);
return View(form);
}
}
}
Now you are able to use [Authorize] annotation on any Action or Controller.
Feel free to comment any questions or bug's.
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.
I would like to add something to brilliant #AmiNadimi answer for everyone who going implement his solution in .NET Core 3:
First of all, you should change signature of SignIn method in UserManager class from:
public async void SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)
to:
public async Task SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)
It's because you should never use async void, especially if you work with HttpContext. Source: Microsoft Docs
The last, but not least, your Configure() method in Startup.cs should contains app.UseAuthorization and app.UseAuthentication in proper order:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
#Manish Jain, I suggest to implement the method with boolean return:
public class UserManager
{
// Additional code here...
public async Task<bool> SignIn(HttpContext httpContext, UserDbModel user)
{
// Additional code here...
// Here the real authentication against a DB or Web Services or whatever
if (user.Email != null)
return false;
ClaimsIdentity identity = new ClaimsIdentity(this.GetUserClaims(dbUserData), CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
// This is for give the authentication cookie to the user when authentication condition was met
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return true;
}
}
Related
I am working on ASP.NET Core Web API. I am trying to create a custom Authorize attribute but I am stuck. I could not understand what I am missing. I have the following code for the Authorize attribute and filter:
public class AuthorizeAttribute : TypeFilterAttribute
{
public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
{
Arguments = new object[] { claim };
}
}
public class AuthorizeFilter : IAuthorizationFilter
{
readonly string[] _claim;
public AuthorizeFilter(params string[] claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;
if (IsAuthenticated)
{
bool flagClaim = false;
foreach (var item in _claim)
{
if (context.HttpContext.User.HasClaim("Role", item))
flagClaim = true;
}
if (!flagClaim)
{
//if (context.HttpContext.Request.IsAjaxRequest())
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; //Set HTTP 403
//else
// context.Result = new RedirectResult("~/Login/Index");
}
}
else
{
//if (context.HttpContext.Request.IsAjaxRequest())
//{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; //Set HTTP 401 -
//}
//else
//{
// context.Result = new RedirectResult("~/Login/Index");
//}
}
return;
}
}
I have copied this code from somewhere and commented unnecessary lines.
Here is my controller class where I am trying to put this:
[Route("api/[controller]/[action]")]
[ApiController]
[Authorize]
public class JobController : ControllerBase
{
// GET: api/<JobController>
[HttpGet]
[ActionName("GetAll")]
public List<Job> Get()
{
return JobDataLog.GetAllJobQueue();
}
// GET api/<JobController>/5
[HttpGet("{ID}")]
[ActionName("GetByID")]
public Job Get(Guid ID)
{
return JobDataLog.GetJob(ID);
}
// GET api/<JobController>/5
[HttpGet]
[ActionName("GetCount")]
public int GetCount()
{
return JobDataLog.GetJobTotal();
}
}
Also the Configure and ConfigureService methods of Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(60);
});
var tokenKey = Configuration.GetValue<string>("TokenKey");
var key = Encoding.ASCII.GetBytes(tokenKey);
services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; })
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddSingleton<IJWTAuthenticationManager>(new JWTAuthenticationManager(tokenKey));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCookiePolicy();
app.UseSession();
app.Use(async (context, next) =>
{
var JWToken = context.Session.GetString("JWToken");
if (!string.IsNullOrEmpty(JWToken))
{
context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
}
await next();
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The problem is that even this controller has the Authorize attribute, all the actions are being called even the Authorize filter invalidates the authorization.
Also when I placed the following code in the OnAuthorization method:
context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
It blocked the access of all actions, including those which have an AllowAnnoynmous attribute.
Please help me, I have been stuck on this for last 3 hours.
If you really want to use a custom AuthorizeAttribute, here you go, this works. :)
You'll have a few squiggly lines, but VS will be able to automatically add the using statements.
The original code had multiple problems:
Setting Reponse.StatusCode doesn't actually lead to a response being returned.
HttpContext.User wouldn't be populated in the first place, because ASP.NET Core only attempts to authenticate the user and populate the user's claims/identity if an endpoint is secured with the built-in AuthorizeAttribute. The following code solves this by deriving from AuthorizeAttribute.
In this case the additional filter factory class wasn't needed, since you're not injecting dependencies. Though, if you had to inject, you'd be out of luck I think, because you couldn't derive both from TypeFilterAttribute and AuthorizeAttribute, and the claims list would be always empty.
Working code
public class MyAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
readonly string[] _requiredClaims;
public MyAuthorizeAttribute(params string[] claims)
{
_requiredClaims = claims;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var isAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
if (!isAuthenticated)
{
context.Result = new UnauthorizedResult();
return;
}
var hasAllRequredClaims = _requiredClaims.All(claim => context.HttpContext.User.HasClaim(x => x.Type == claim));
if (!hasAllRequredClaims)
{
context.Result = new ForbidResult();
return;
}
}
}
You should probably use policies instead
The reason why this works in such a crappy way is that the ASP.NET Core team doesn't want you to write custom Authorize Attributes. See this answer on the subject. The 'proper' way is to create policies, and assign your claim requirements to those policies. But I also think it's silly that authorization is so inflexible and lacking support for basic scenarios.
I have been trying to implement basic authentication into my Razor 3.0 web app.
I have a user being signed in via SignInAsync(), however when trying to redirect to a page that requires Authorization, the page does not load. Instead the Login page reloads, with the URL being updated with a "?ReturnUrl=%Page%Path" appended.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/Admin");
options.Conventions.AllowAnonymousToPage("/Login");
options.Conventions.AddPageRoute("/Login", "");
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
Here I am setting the Admin folder to require authorization, and allowing anonymous users to access the login page.
Login.cshtml.cs
[BindProperty]
public UserModel UserLogin { get; set; }
public async Task<IActionResult> OnPost()
{
try
{
if (ModelState.IsValid)
{
var loginInfo = apiConnector.Get();
if (loginInfo)
{
await this.SignInUser(UserLogin.Username, false);
return this.RedirectToPage("/Admin/FormOverview");
}
}
}
catch(Exception e)
{
Console.Write(e);
}
return this.Page();
}
private async Task SignInUser(string username, bool isPersistant)
{
try
{
var claims = new List<Claim>() {
new Claim(ClaimTypes.Name, username)
};
var claimIdentities = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimPrincipal = new ClaimsPrincipal(claimIdentities);
var authProperties = new AuthenticationProperties
{
AllowRefresh = true,
IsPersistent = true,
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimPrincipal, authProperties);
}
Here I've set up a dummy API which always returns true, with the user having a Claim created them and signing them in.
I have tried debugging through and I can see that the User.Identity.IsAuthenticated is being set to true correctly
when I remove Authorization from the Admin folder the page redirects correctly
The Claim on the user is being registered after sign-in
I am new to Razor so I may be approaching this incorrectly, any help appreciated, thanks.
Order matters
app.UseAuthentication();
app.UseAuthorization();
When I successfully signed in to the HTTP context and I'm redirected to the home controller, the Authorize Attribute of the HomeController redirects me to the login path, because I don't know. (Infinite loop)
The validate async method doesn't sign me out or reject the cookie (I checked it.)
But where's the problem?
I have the same authentication process in an ASP.NET Core 2.1 project and there it works perfectly.
I test it with a custom authorize attribute to check the context. In the context I've a principal and I'm authenticated.
But why redirect me the standard authorize attribute?
My configuration is:
HomeController.cs
public class HomeController : Controller
{
[Authorize]
public IActionResult Index()
{
return this.View("Index");
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/auth/login/";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.Events.OnValidatePrincipal = ValidateAsync;
});
services.AddControllersWithViews();
services.AddAntiforgery();
services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
context = context ?? throw new ArgumentNullException(nameof(context));
String userId = context.Principal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;
if(userId == null)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
ApplicationDbContext dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
User user = await dbContext.Users.FindAsync(Guid.Parse(userId));
if(user == null)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
if(!user.StaySignedIn &&
user.LastLogin != null &&
(user.LastLogin.Subtract(TimeSpan.FromDays(1)) > DateTimeOffset.Now))
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
}
AuthController.cs
[Route("/login")]
[Route("/auth/login")]
public async Task<IActionResult> Login([FromForm]LoginModel loginModel)
{
Claim nameIdentifier = new Claim(ClaimTypes.NameIdentifier, user.Id.ToString());
ClaimsIdentity userIdentity = new ClaimsIdentity(new List<Claim> { nameIdentifier }, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(userIdentity);
await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
return this.RedirectToAction("Index", "Home");
}
I found the solution for my problem.
The order of
app.UseAuthorization();
app.UseAuthentication();
Must be changed and then it works.
The issue closed When using identity is not allowed to use the cookies.
It overrides some of the information.
Git Discussion
Cookie options tell the authentication middleware how the cookie works in the browser.In the Startup class, add this code in your ConfigureServices Method
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options => { options.LoginPath = "/Login"; });
I am trying to setup authentication using cookies but SignInAsync seems to not be working.
My sign method is, at the moment at least, like this:
[HttpPost]
public async Task<IActionResult> Login(string username, string password)
{
if (username != "foo" || password != "bar")
{
return View();
}
//Sign in
var claims = new List<Claim>
{
new Claim("name", "admin")
};
var userIdentity = new ClaimsIdentity(claims, "login");
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
await HttpContext.SignInAsync(principal);
//Redirect
return RedirectToAction("home");
}
Home is an action with Authorize attribute.
If I provide the correct username and password, I can step over the SignInAsync call with no exception seemingly called. However, upon redirection, I end up back at the login page as I am still not logged in.
Startup.cs is listed below:
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/admin/login/";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=home}/{action=index}/{id?}");
});
app.UseAuthentication();
}
}
What am I missing or how can I debug/get further information on what is going wrong?
As per the comment by Tratcher, use app.UseAuthentication(); before app.UseMvc();.
I am working on a web app that needs to integrate with an existing user database. I would still like to use the [Authorize] attributes, but I don't want to use the Identity framework. If I did want to use the Identity framework I would add something like this in the startup.cs file:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireNonLetterOrDigit = false;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
I'm assuming I have to add something else there, and then create some kind of class that implements a specific interface? Can somebody point me in the right direction? I'm using RC1 of of asp.net 5 right now.
From what I learned after several days of research,
Here is the Guide for ASP .Net Core MVC 2.x Custom User Authentication
In Startup.cs :
Add below lines to ConfigureServices method :
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CookieAuthenticationDefaults.AuthenticationScheme
).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = "/Account/Login";
options.LogoutPath = "/Account/Logout";
});
services.AddMvc();
// authentication
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
services.AddTransient(
m => new UserManager(
Configuration
.GetValue<string>(
DEFAULT_CONNECTIONSTRING //this is a string constant
)
)
);
services.AddDistributedMemoryCache();
}
keep in mind that in above code we said that if any unauthenticated user requests an action which is annotated with [Authorize] , they well force redirect to /Account/Login url.
Add below lines to Configure method :
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler(ERROR_URL);
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: DEFAULT_ROUTING);
});
}
Create your UserManager class that will also manage login and logout. it should look like below snippet (note that i'm using dapper):
public class UserManager
{
string _connectionString;
public UserManager(string connectionString)
{
_connectionString = connectionString;
}
public async void SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)
{
using (var con = new SqlConnection(_connectionString))
{
var queryString = "sp_user_login";
var dbUserData = con.Query<UserDbModel>(
queryString,
new
{
UserEmail = user.UserEmail,
UserPassword = user.UserPassword,
UserCellphone = user.UserCellphone
},
commandType: CommandType.StoredProcedure
).FirstOrDefault();
ClaimsIdentity identity = new ClaimsIdentity(this.GetUserClaims(dbUserData), CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
}
}
public async void SignOut(HttpContext httpContext)
{
await httpContext.SignOutAsync();
}
private IEnumerable<Claim> GetUserClaims(UserDbModel user)
{
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id().ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserFirstName));
claims.Add(new Claim(ClaimTypes.Email, user.UserEmail));
claims.AddRange(this.GetUserRoleClaims(user));
return claims;
}
private IEnumerable<Claim> GetUserRoleClaims(UserDbModel user)
{
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id().ToString()));
claims.Add(new Claim(ClaimTypes.Role, user.UserPermissionType.ToString()));
return claims;
}
}
Then maybe you have an AccountController which has a Login Action that should look like below :
public class AccountController : Controller
{
UserManager _userManager;
public AccountController(UserManager userManager)
{
_userManager = userManager;
}
[HttpPost]
public IActionResult LogIn(LogInViewModel form)
{
if (!ModelState.IsValid)
return View(form);
try
{
//authenticate
var user = new UserDbModel()
{
UserEmail = form.Email,
UserCellphone = form.Cellphone,
UserPassword = form.Password
};
_userManager.SignIn(this.HttpContext, user);
return RedirectToAction("Search", "Home", null);
}
catch (Exception ex)
{
ModelState.AddModelError("summary", ex.Message);
return View(form);
}
}
}
Now you are able to use [Authorize] annotation on any Action or Controller.
Feel free to comment any questions or bug's.
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.
I would like to add something to brilliant #AmiNadimi answer for everyone who going implement his solution in .NET Core 3:
First of all, you should change signature of SignIn method in UserManager class from:
public async void SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)
to:
public async Task SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)
It's because you should never use async void, especially if you work with HttpContext. Source: Microsoft Docs
The last, but not least, your Configure() method in Startup.cs should contains app.UseAuthorization and app.UseAuthentication in proper order:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
#Manish Jain, I suggest to implement the method with boolean return:
public class UserManager
{
// Additional code here...
public async Task<bool> SignIn(HttpContext httpContext, UserDbModel user)
{
// Additional code here...
// Here the real authentication against a DB or Web Services or whatever
if (user.Email != null)
return false;
ClaimsIdentity identity = new ClaimsIdentity(this.GetUserClaims(dbUserData), CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
// This is for give the authentication cookie to the user when authentication condition was met
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return true;
}
}