I am trying to add a fake user when running the application in development mode. Insted of adding a fake JWT header is there a way to add some fake user details?
This is what I have done so far:
I created a fake filter:
internal class FakeUserFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
context.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new(ClaimTypes.NameIdentifier, "123"),
new(ClaimTypes.Name, "Test user"),
new(ClaimTypes.Email, "test#example.com"),
new(ClaimTypes.Role, "Admin")
}));
await next();
}
}
Added it to Controllers
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
//...
builder.Services.AddControllers(options =>
{
if (!builder.Configuration.GetValue<bool>("UseAuthorization"))
{
options.Filters.Add(new AllowAnonymousFilter());
options.Filters.Add(new FakeUserFilter());
}
});
//...
And later in the I enabled Authentication and Authorization
//...
app.UseAuthentication();
app.UseAuthorization();
//...
I still get an unauthorised error. As of now, I am just adding a global anonymous attribute by doing
if (app.Environment.IsDevelopment())
{
app.MapControllers().WithMetadata(new AllowAnonymousAttribute());
}
else
{
app.MapControllers();
}
This obviously doesn't give me a fake user. Is there a way to set the fake user in development mode? Any help is appreciated.
ActionFilter is not the right tool for this job. What you should do is to fake authentication handler. Just write some kind of TestAuthenticationHandler implementing AuthenticationHandler class.
Example:
public class TestAuthHandlerOptions : AuthenticationSchemeOptions
{
}
public class TestAuthHandler : AuthenticationHandler<TestAuthHandlerOptions>
{
public const string AuthenticationScheme = "Test";
public TestAuthHandler(IOptionsMonitor<TestAuthHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, "123"),
new(ClaimTypes.Name, "Test user"),
new(ClaimTypes.Email, "test#example.com"),
new(ClaimTypes.Role, "Admin")
};
var identity = new ClaimsIdentity(claims, AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, AuthenticationScheme);
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
And register it:
builder.Services.AddAuthentication(TestAuthHandler.AuthenticationScheme)
.AddScheme<TestAuthHandlerOptions, TestAuthHandler>(TestAuthHandler.AuthenticationScheme, options => { });
And that's it. In such a way, you're going to have access to your fake user at HttpContext.
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 want to log stats for all calls to my .netcore webapi.
I have added a IAsyncActionFilter for this purpose and it picks up on all the actions.
But I also have Jwt Bearer Authentication enabled and am using the AuthorizeAttribute on my controller to limit access. When access is denied the Action filter will not be hit.
Whats the best way to add some custom logging (statsd) for authentication in general and failures in particular?
public void ConfigureServices(IServiceCollection services)
{
....
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = Configuration["Authority"]; ;
// name of the API resource
options.Audience = "myAudience";
});
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
...
}
I Notice that JwtBearerOptions has JwtBearerEvents Events but I cant get this to work.
Edit : It looks like I am hitting the api with no token at all and the JWT Auth handler returns AuthenticateResult.NoResult() without calling the Events.AuthenticationFailed
https://github.com/aspnet/Security/blob/ba1eb281d135400436c52c17edc71307bc038ec0/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L63-L83
Edit 2 : Very frustrating. Looks like the correct place to log would be in Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter But this is automatically added when you use [Authorize] and is impossible to override, remove or replace?
The JwtBearerOptions.cs class exposes an JwtBearerEvents parameter where you can declare your events like this
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = Configuration["Authority"]; ;
// name of the API resource
options.Audience = "myAudience";
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Log failed authentications
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
//Log successful authentications
return Task.CompletedTask;
}
};
});
I don´t have any experience with Jwt Bearer Authentication - but we´ve had a similar situation. We´ve ended up with a new BaseController where we´re able to Log basic user information and distinguish between [AllowAnonymous] and [Authorize]:
public class NewBaseController : Controller
{
protected UserManager<MyUser> UserManager;
protected MyUser CurrentUser;
protected ILogger Logger;
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
UserManager = context.HttpContext.RequestServices.GetService<UserManager<MyUser>>();
var loggerFactory = context.HttpContext.RequestServices.GetService<ILoggerFactory>();
Logger = loggerFactory.CreateLogger(GetType());
// Check if Action is annotated with [AllowAnonymous]
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var anonymousAllowed = controllerActionDescriptor.MethodInfo
.GetCustomAttributes(inherit: true)
.Any(a => a.GetType().Equals(typeof(AllowAnonymousAttribute)));
if (!anonymousAllowed)
{
ApplicationUser = UserManager.GetUserAsync(User).Result;
if (ApplicationUser == null)
// do some stuff
Logger.LogInformation("User is {UserId}", CurrentUser.Id);
}
else
{
Logger.LogInformation("User is {User}", anonymous);
}
}
}
Bonus: Every Controller that derives from this base already has an instance of UserManager, ILogger and CurrentUser.
Hope that comes close to what you´re looking for...
In my ASP.Net Core 2 project, I have a custom AuthenticationHandler middleware that i want to plug in.
public class BasicAuthenticationMiddleware : AuthenticationHandler<AuthenticationSchemeOptions>
{
public BasicAuthenticationMiddleware(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var principal = new GenericPrincipal(new GenericIdentity("User"), null);
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), "BasicAuth");
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
In my startup I have the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "BasicAuth";
options.DefaultChallengeScheme = "BasicAuth";
options.AddScheme("BasicAuth", x => {
x.DisplayName = "BasicAuthenticationMiddleware";
x.HandlerType = typeof(BasicAuthenticationMiddleware);
});
});
}
And finally my view controller:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values/Works
[HttpGet]
[Route("Works")]
[Authorize(ActiveAuthenticationSchemes = "BasicAuth")]
public string Works()
{
return "works";
}
// GET api/values/DoesNotWork
[HttpGet]
[Route("DoesNotWork")]
[Authorize]
public string DoesNotWork()
{
return "does not work";
}
}
My authenticator HandleAuthenticateAsync will be called when I specify ActiveAuthenticationSchemes to my scheme name, but otherwise it will not. I have a demo app showing the behavior here: https://github.com/JohnPAguirre/AuthenticationSchemaProblem
I want my BasicAuthenticationMiddleware to log everyone in with my demo logic. How can i make the ActiveAuthenticationSchemes default to "BasicAuth" for all requests?
Anyone have any ideas on what I could be missing?
I did manage to set a default authentication scheme By setting the scheme I want as the only authentication scheme of the DefaultPolicy for authorization. Use the following below in your config. I have used it in between AddMvc and AddAuthentication and it works fine.
services.AddAuthorization(config => {
var def = config.DefaultPolicy;
config.DefaultPolicy = new AuthorizationPolicy(def.Requirements,
new List<string>(){ "BasicAuth" });
});
I don't think you can set a default, but you have some other options.
Create your own custom authorisation attribute:
public class BasicAuthAuthorizeAttribute : AuthorizeAttribute
{
public BasicAuthAuthorizeAttribute()
{
ActiveAuthenticationSchemes = "BasicAuth";
}
}
And use it on your actions like you would before:
[BasicAuthAuthorize]
public string SomeAction()
{
//snip
}
Add the Authorize attribute to all your actions and only override it where needed. To do that, in your `` method:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizeAttribute
{
ActiveAuthenticationSchemes = "BasicAuth"
});
});
//snip
}
And overriding it:
[AllowAnonymous]
public string UnsecureAction()
{
//snip
}
I have used a similar code and it works perfectly. Main difference I see is I used the AddScheme function chaining AddAuthentication instead of inside it's config.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "BasicAuth";
options.DefaultChallengeScheme = "BasicAuth";
})
AddScheme<AuthenticationSchemeOptions, BasicAuthenticationMiddleware>
("BasicAuth", "BasicAuthenticationMiddleware", x => { });
Rest of the code seems fine.
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;
}
}
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;
}
}