I am implementing two factor authentication in my ASP.NET MVC 5 application for all users. I have already implemented single factor (username and password) authentication using ASP.NET Identity.
Here is how I implemented single factor authentication:
Step 1: I created the startup.cs file to know application should use cookie authentication
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
LogoutPath = new PathString("/Account/LogOut")
});
}
}
Step 2: User makes post request to login to application by sending username and password. Once username and password are verified, I used sign method from Owin Authentication Manager to sign in the user. This sign in method creates cookie and helps the authentication.
public ActionResult Login(LoginViewModel model)
{
Session.Clear();
if (!ModelState.IsValid)
{
return View(model);
}
var userManager = new ApplicationUserManager(new ApplicationUserStore(new ApplicationDbContext()));
var user = userManager.Find(model.Username,model.Password);
if (user != null)
{
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
var authenticationManager = HttpContext.GetOwinContext().Authentication;
authenticationManager.SignIn(new AuthenticationProperties(), userIdentity);
}
}
But now I want to introduce OTP authentication after username and password login.I know how to generate OPT and I have SMS provider to send OTP to the user. I can easily put authenticationManager.SignIn() method after OTP verification. But my question is, is there any separate configuration we need provide during signIn to know application that we are doing two factor authentication.
If yes can somebody provide the code where to make such configuration?
I have created a web application with ASP.NET Core 3.1 and angular with the Visual Studio Template and enabled Authentication.
Then I scaffolded the Identity and added this to the Startup.cs:
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer().AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication().AddIdentityServerJwt();
After that I have created 3 users and assigned them different roles. When I add the Attribute [Authorize] to any controller action it works correctly. But how can I make it role based? I have tried [Authorize(Roles = "Administrator")] But it denied the access. On an unprotected method:
var loggedinUser = await _userManager.FindByNameAsync(userid);
var roles = await _userManager.GetRolesAsync(loggedinUser);
the roles list has the Administrator on it, so I do not know what I am missing to make it work. Also inside an Angular component, how can I get the role of the current logged in user?
You can extend the implementation of default ProfileService of IdentityServer to include role claim like following way.
public class ExtendedProfileService : ProfileService<ApplicationUser>
{
public ExtendedProfileService(UserManager<ApplicationUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory) : base(userManager, claimsFactory)
{
}
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
await base.GetProfileDataAsync(context);
var user = await UserManager.GetUserAsync(context.Subject);
var roles = await UserManager.GetRolesAsync(user);
var claims = new List<Claim>();
foreach (var role in roles)
{
claims.Add(new Claim("role", role));
}
context.IssuedClaims.AddRange(claims);
}
}
And don't forget to register your extended profile service to dependency container
services.AddTransient<IProfileService, ExtendedProfileService>();
I am new to ASP.NET core itself. However, I am creating WebAPIs in ASP.NET Core 2.0. I have configured JWT Bearer Token based authentication. Below is my Controller which return token.
[AllowAnonymous]
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly UserManager<UserEntity> userManager;
private readonly SignInManager<UserEntity> signInManager;
public TokenController(UserManager<UserEntity> userManager, SignInManager<UserEntity> signInManager)
{
this.userManager = userManager;
this.signInManager = signInManager;
}
// GET: api/values
[HttpGet]
public async Task<IActionResult> Get(string username, string password, string grant_type)
{
{
var user = await userManager.FindByEmailAsync(username);
if (user != null)
{
var result =await signInManager.CheckPasswordSignInAsync(user, password, false);
if (result.Succeeded)
{
var claims = new[]
{
new Claim( JwtRegisteredClaimNames.Sub, username),
new Claim( JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim( JwtRegisteredClaimNames.GivenName, "SomeUserID")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secretesecretesecretesecretesecretesecrete"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken( issuer: "test",
audience: "test",
claims: claims,
expires: DateTime.Now.AddDays(15),
signingCredentials: creds);
return Ok(new { access_token = new JwtSecurityTokenHandler().WriteToken(token), expires_on=DateTime.Now.AddDays(15) });
}
}
}
return BadRequest("Could not create token");
}
}
But when calling ValuesController API which is decorated with [Authorize] attributes. I am getting User.Identity.Name is empty. I am not getting any information about user. I am not sure, My token controller is correctly written. As long as it is protecting my ValuesController, I assume, it is correct. However, I might be missing something. Please help.
Note: I am developing using Visual Studio 2017 with Mac Community
addition
Yes, you need to specify the claim for the unique name which is translated into the user.identity.name:
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
I've also been having this problem with ASP.Net Core 2, and I'm really surprised no one's discovered the other cause of this problem.
When my webapp is deployed to IIS, "User.Identity.Name" always returns null. The IIS site has anonymous access disabled, and windows authentication is enabled.
BUT.
I didn't realise that my ASP.Net Core 2 has a "launchSettings.json" file, quietly hidden under the Properties folder, and in there, there's also some iisSettings, and in here "windowsAuthentication" was, strangely, set as false by default.
Changing "windowsAuthentication" to true, and "anonymousAuthentication" to false solved the problem for me.
After doing this, "User.Identity.Name" did finally contain the correct username.
But what the heck is this setting ? Why would this get priority over the actual settings we've setup in IIS Manager ?!
Had this problem too (Core 3.1) using the "DefaultIdentity" (Individual User Accounts).
User.Identity.Name is null, User.Identity.IsAuthenticated = true.
By using httpContextAccessor you can get the userId an with that id you can find the user and the UserName.
In your controller add
using System.Security.Claims;
...
private readonly IHttpContextAccessor _httpContextAccessor;
public MyController(MyContext context, IHttpContextAccessor httpContextAccessor)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}
// Any method username needed
[HttpGet("{id}")]
public async Task<ActionResult<MyInfo>> GetMyInfo(int id)
{
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
var user = _context.AspNetUsers.Find(userId);
var userName = user.UserName;
...
}
In the Startup.cs add the following line:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
For Azure OAuth v2, use preferred_username instead of unique_name (see this and this).
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
serviceCollection.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.RoleClaimType = "roles";
options.TokenValidationParameters.NameClaimType = "preferred_username";
//options.TokenValidationParameters.NameClaimType = "email"; // or if you want to use user's email for User.Identity.Name
//below lines of code can be removed. just there if you want some code to be executed right after user is validated.
options.Events.OnTokenValidated = async context =>
{
var personFirstName = context.Principal.FindFirstValue("given_name") ?? string.Empty;
var personLastName = context.Principal.FindFirstValue("family_name") ?? string.Empty;
var personEmail = context.Principal.FindFirstValue("email")?.ToLower();
var personName = context.Principal.Identity.Name;
};
});
Then in your controllers, you will get username from User.Identity.Name
I am working with an existing authentication API and repository that currently only exposes two methods.
Task<User> Login(string username, string password);
Task Logout(User user);
I would like to use this with asp.net core Identity. All the application needs the ability to do is Log In, Log Out and show the current user who is logged in.
This is my current idea on how to do this.
Extend the SignInManager class as so
public class MySignInManager : SignInManager<User>
{
public Override async Task<SignInResult> PasswordSignInAsync(string userName, string password,
bool isPersistent, bool lockoutOnFailure)
{
var user = await Login(userName, password);
if (user != null)
{
return SignInResult.Success;
}
else
{
return SignInResult.Failure;
}
}
}
Then in my startup would look like this this:
services.AddIdentity<User, IdentityRole>()
.AddDefaultTokenProviders();
services.AddTransient<MySignInManager>();
I read over this article :
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers
and they suggested to implement the IUserStore and deal with the existing API from there, but after reading through the default implmentation of the SignInManager and UserManager, it seems to check the Username and password in seperate methods.
Sorry for my lack of knowledge on the subject, I am quite new to the Identity server. Any Help would be greatly appreciated!
My goal is:
To use custom headers with my own token to authenticate a user or machine against my signalr service.
We've been using this methodology succesfully under ASP.net WEB API to perform our own custom claims based authentication and authorization.
Our Web Api was as follows:
protected void Application_Start()
{
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthorizationHeaderHandler());
}
Then we would have a AuthorizationHandler that would overwrite the Thread.CurrentPrincipal = principal; and we would be done.
Within SignalR I have tried to implement:
1. Mark our hub using Authorize
2. Implemented custom authorize atributes
3. Tried A Custom Module. But besides returning true if the correct headers we're send I still do not get the Context.User to change to the claims based principal that we generate.
But never can we get the Context.User to show the actual user that's being used to connect to the hub.
Any suggestions are Welcome.
Main reason why we want to achieve this is because we have a couple of different user/machine types that connect to our system.
Anybody any suggestions.
Finally found the solution.
I added my own owin security middleware allowing me to handle customer header based authentication.
This could be easily expanded allowing you to combine multiple authenitication scheme's within on service.
First Create Custom Authentication Middleware:
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) :
base(next) { }
public override async Task Invoke(IOwinContext context)
{
var request = context.Request;
var value = request.Headers["Phocabby-MachineKey"];
var username = value;
var usernameClaim = new Claim(ClaimTypes.Name, username);
var identity = new ClaimsIdentity(new[] { usernameClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
principal.Identities.First().AddClaim(new Claim("CanGetApiKey", "False"));
principal.Identities.First().AddClaim(new Claim("Cabinet", "True"));
request.User = principal;
await Next.Invoke(context);
}
}
Then register it in the startup class
public void Configuration(IAppBuilder app)
{
app.Use(typeof(AuthenticationMiddleware));
app.MapSignalR();
}