Asp.Net Web API with .net core is updating the password automatically on JWT token generation.
So firstly, I had an MVC5 application with asp net membership tables, and wanted to create an API for the same with .net core.
And to support both MVC5 Web APP and WEB API. I added four more columns for AspNetUsers table (ConcurrencyStamp, LockoutEnd, NormalizedEmail, NormalizedUserName).
Although I'm able to get JWT token without any issues, it's also updating the password each time I generate the JWT token which is not allowing users to login from MV5 web APP.
Below is the JWT generate token code
[Route("login")] // /login
[HttpPost]
public async Task<ActionResult> Login([FromBody] LoginViewModel
model)
{
try
{
var user = await
_userManager.FindByNameAsync(model.Username);
if (user != null && await
_userManager.CheckPasswordAsync(user, model.Password))
{
var claim = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.Id)
};
var signinKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:SigningKey"]));
int expiryInMinutes =
Convert.ToInt32(_configuration["Jwt:ExpiryInMinutes"]);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Site"],
claims: claim,
audience: _configuration["Jwt:Site"],
expires: DateTime.UtcNow.AddMinutes(expiryInMinutes),
signingCredentials: new SigningCredentials(signinKey,
SecurityAlgorithms.HmacSha256)
);
return Ok(
new
{
token = new
JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo,
userName = user.UserName
});
}
return Unauthorized();
}
catch (Exception ex)
{
return Unauthorized();
}
}
Please let me know how to stop updating the PasswordHash and SecurityStamp column in AspNetUsers on generating JWT token.
Update: CheckPasswordAsync(used in web API) method is updating the password field and PasswordSignInAsync method is used in web app
#KirkLarin, thanks a lot and it helped me to solve the problem by adding the below code in StartUp.cs file under Configure service method
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PasswordHasherOptions>(options => options.CompatibilityMode =
PasswordHasherCompatibilityMode.IdentityV2);
}
Related
I am using asp.net core api 2.2 and I am using token authentication.
I generate the tokens like this
Claim[] claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, employee.Id),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat,new DateTimeOffset(now).ToUnixTimeSeconds().ToString()),
new Claim(ClaimTypes.Role, allRoles)
// TODO: add additional claims here
};
var tokenExpirationMins = configuration.GetValue<int>("Auth:Jwt:TokenExpirationInMinutes");
SymmetricSecurityKey issuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Auth:Jwt:Key"]));
JwtSecurityToken token = new JwtSecurityToken(
issuer: configuration["Auth:Jwt:Issuer"],
audience: configuration["Auth:Jwt:Audience"],
claims: claims,
notBefore: now,
expires: now.Add(TimeSpan.FromMinutes(tokenExpirationMins)),
signingCredentials: new SigningCredentials(
issuerSigningKey, SecurityAlgorithms.HmacSha256)
);
string encodedToken = new JwtSecurityTokenHandler().WriteToken(token);
I store the response data in LocalStorage and when I do an ajax request I send the "Bearer Access Token" to the server and then the built in Asp.net Identity / Core take care of authenticating it via the Authorize tags.
In the controller I have something like this
[AllowAnonymous]
[HttpGet("GetItems")]
public IActionResult GetItems()
{
var isMember = User.FindFirst(ClaimTypes.NameIdentifier) != null;
return OK();
}
Now this method is used by logged in users and logged out users. Basically the code is all the same except if they are a "member" they see a bit more data.
Now what I noticed is that when a "member" would login and go to this area and gets the "Items" back with all the fields shown and then logout and go back to that page the line of code that checks if they got a Claim will still be populated.
var isMember = User.FindFirst(ClaimTypes.NameIdentifier) != null;
If I where to refresh the page and go back to it then this would be null. For whatever reason it still holding onto the claims.
This is leading to them see all fields when they should only be seeing a reduced amount of fields as they logged out.
Do I have to clear something else on logout?
I'm building a web application which uses the cookie authentication built into ASP.NET Core 2.1.
I have my own sign in method which queries my own custom password verification and setting of claims. Roughly it looks like this:
public async Task<ActionResult<LoginResponse>> DoLogin([FromBody] LoginRequest req)
{
// fetch account and verify password
var claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, account.AccountId.ToString(), ClaimValueTypes.Integer),
new Claim(ClaimTypes.Email, account.EmailAddress, ClaimValueTypes.Email),
new Claim(ClaimTypes.Role, "member", ClaimValueTypes.String)
};
var identity = new ClaimsIdentity(claims, "password");
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return new LoginResponse
{
Success = true
};
}
I would like to conditionally render a "Log Out" button on various parts of the site if the user has the cookie which authenticates the user. Additionally I'd like to fetch the Sid claim so I can deliver personalized messaging on some public parts of the site.
The problem I have is that the way I have been fetching the Sid only works if my controller or controller action has an [Authorize] attribute on it. Without the [Authorize] attribute, the claim is missing.
Code:
public static int? GetNullableAccountId(this ClaimsPrincipal principal)
{
var claim = principal.FindFirst((Claim c) => { return c.Type == ClaimTypes.Sid; });
if (claim == null)
return null;
return int.Parse(claim.Value);
}
// then in the controller I try to get the account id:
var accountId = accessor.HttpContext.User.GetNullableAccountId();
// always null even when I have a valid cookie
I swear that I didn't need the [Authorize] attribute for this to work in prior versions of ASP.NET Core, but I couldn't find anything meaningful in change logs.
Is there some trick to getting ASP.NET Core to build the user identity on all calls or am I taking the wrong approach all together?
It seems it was a silly mistake. I was invoking app.UseAuthentication() after app.UseMvc() when configuring my application builder.
The documentation actually explicitly states the following:
Call the UseAuthentication method before calling UseMvcWithDefaultRoute or UseMvc
Source: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-2.2#configuration
I have the following useful load in a token generated with JWT
{
"sub": "flamelsoft#gmail.com",
"jti": "0bca1034-f3ce-4f72-bd91-65c1a61924c4",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Administrator",
"exp": 1509480891,
"iss": "http://localhost:40528",
"aud": "http://localhost:40528"
}
with this code
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DBContextSCM>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"), b =>
b.MigrationsAssembly("FlamelsoftSCM")));
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<DBContextSCM>()
.AddDefaultTokenProviders();
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
services.AddMvc();
}
AccountController.cs
[HttpPost]
[Authorize(Roles="Administrator")]
public async Task<IActionResult> Register([FromBody]RegisterModel model)
{
try
{
var user = new User { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var role = await _roleManager.FindByIdAsync(model.Role);
result = await _userManager.AddToRoleAsync(user, role.Name);
if (result.Succeeded)
return View(model);
}
return BadRequest($"Error: Could not create user");
}
catch (Exception ex)
{
return BadRequest($"Error: {ex.Message}");
}
}
user.service.ts
export class UserService {
constructor(private http: Http, private config: AppConfig, private currentUser: User) { }
create(user: User) {
return this.http.post(this.config.apiUrl + 'Account/Register', user, this.jwt());
}
private jwt() {
const userJson = localStorage.getItem('currentUser');
this.currentUser = userJson !== null ? JSON.parse(userJson) : new User();
if (this.currentUser && this.currentUser.token) {
let headers = new Headers({ 'Authorization': 'Bearer ' + this.currentUser.token });
return new RequestOptions({ headers: headers });
}
}}
The problem is that the validation of the role does not work, the request arrives at the controller and returns a code 200 in the header, but never enters the class.
When I remove the [Authorize (Roles = "Administrator")] it enters correctly my code.
Is there something badly defined? Or what would be the alternative to define an authorization through roles.
TL;DR
As mentioned in the comments of the original question, changing:
[HttpPost]
[Authorize(Roles = "Administrator")]
public async Task<IActionResult> Register([FromBody]RegisterModel model)
{
// Code
}
to
[HttpPost]
[Authorize(AuthenticationSchemes = "Bearer", Roles = "Administrator")]
public async Task<IActionResult> Register([FromBody]RegisterModel model)
{
// Code
}
resolved the issue.
Bearer is the default authentication scheme name when using JWT bearer authentication in ASP.NET Core.
But why do we need to specify the AuthenticationSchemes property on the [Authorize] attribute?
It's because configuring authentication schemes doesn't mean they will run on each HTTP request. If a specific action is accessible to anonymous users, why bother extract user information from a cookie or a token? MVC is smart about this and will only run authentication handlers when it's needed, that is, during requests that are somehow protected.
In our case, MVC discovers the [Authorize] attribute, hence knows it has to run authentication and authorization to determine if the request is authorized or not. The trick lies in the fact that it will only run the authentication schemes handlers which have been specified. Here, we had none, so no authentication was performed, which meant authorization failed since the request was considered anonymous.
Adding the authentication scheme to the attribute instructed MVC to run that handler, which extracted user information from the token in the HTTP request, which lead to the Administrator role being discovered, and the request was allowed.
As a side note, there's another way to achieve this, without resorting to using the AuthenticationSchemes property of the [Authorize] attribute.
Imagine that your application only has one authentication scheme configured, it would be a pain to have to specify that AuthenticationSchemes property on every [Authorize] attribute.
With ASP.NET Core, you can configure a default authentication scheme. Doing so implies that the associated handler will be run for each HTTP request, regardless of whether the resource is protected or not.
Setting this up is done in two parts:
public class Startup
{
public void ConfiguresServices(IServiceCollection services)
{
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme /* this sets the default authentication scheme */)
.AddJwtBearer(options =>
{
// Configure options here
});
}
public void Configure(IApplicationBuilder app)
{
// This inserts the middleware that will execute the
// default authentication scheme handler on every request
app.UseAuthentication();
app.UseMvc();
}
}
Doing this means that by the time MVC evaluates whether the request is authorized or not, authentication will have taken place already, so not specifying any value for the AuthenticationSchemes property of the [Authorize] attribute won't be a problem.
The authorization part of the process will still run and check against the authenticated user whether they're part of the Administrator group or not.
I know this question already has an answer, but something important is left out here. You need to make sure you're actually setting the claims for the logged in user. In my case, I'm using JWT Authentication, so this step is very important:
var claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, user.UserName) });
var roles = await _userManager.GetRolesAsync(user);
if (roles.Count > 0)
{
foreach (var role in roles) { claims.AddClaim(new Claim(ClaimTypes.Role, role)); }
}
var token = new JwtSecurityToken(
issuer: _configuration["JWT:Issuer"],
audience: _configuration["JWT:Audience"],
expires: DateTime.UtcNow.AddMinutes(15),
signingCredentials: signingCredentials,
claims: claims.Claims);
I was banging my head trying to figure out why HttpContext.User didn't include what I expected trying to narrow down the [Authroization(Roles="Admin")] issue. Turns out, if you're using JWT Auth you need to remember to set the Claims[] to the identity. Maybe this is done automatically in other dotnet ways, but jwt seems to require you to set that manually.
After I set the claims for the user, the [Authorize(Roles = "Whatever")] worked as expected.
I am just starting to use ASP.Net MVC5 and I am having a little challenge.
I have an already built system that uses WebForms and I am migrating it to MVC.
I understand that MVC provides an Authentication and Authorization provider, but I want to implement a simple login based on the database I already have.
In my previous system, when a user logs in, the id and role of the user is stored in a session variable and checked across all pages. The id is also used for some tasks in the app.
Is there a way to use MVC Identity Management with my existing database?
I tried the following from a previous post but can't seem to get it working.
[HttpPost]
public ActionResult Login(string username, string password)
{
if (new UserManager.IsValid(username, password))
{
var ident = new ClaimsIdentity(
new[] {
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, username),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
new Claim(ClaimTypes.Name,username),
// optionally you could add roles if any
new Claim(ClaimTypes.Role, "RoleName"),
new Claim(ClaimTypes.Role, "AnotherRole"),
},
DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
return RedirectToAction("MyAction"); // auth succeed
}
// invalid username or password
ModelState.AddModelError("", "invalid username or password");
return View();
}
class UserManager
{
public bool IsValid(string username, string password)
{
using(var db=new MyDbContext()) // use your DbConext
{
// if your users set name is Users
return db.Users.Any(u=>u.Username==username
&& u.Password==password);
}
}
}
Please any help and suggestions will be appreciated.
I'm writing a Web API with ASP.NET and Identity 2.0 right know. The API should be only accessible, if a user is 'logged in' successfully. The login is working wonderful, but the logout (signout) doesn't seem to work. Here's some code I'm using:
Identity Configuration:
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext<IdentityDbContext<IdentityUser>>(HLAccountManager.CreateDbContext);
app.CreatePerOwinContext<UserManager<IdentityUser>>(HLAccountManager.CreateUserManager);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
GlobalConfiguration.Configuration.SuppressDefaultHostAuthentication();
GlobalConfiguration.Configuration.Filters.Add(new HostAuthenticationFilter("Bearer"));
}
Authentication Controller:
[HttpPost]
[ActionName("Authenticate")]
[AllowAnonymous]
public String Authenticate(JObject data)
{
dynamic json = data;
string user = json.user;
string password = json.password;
if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password))
return "failed";
var userIdentity = UserManager.FindAsync(user, password).Result;
if (userIdentity != null)
{
var identity = new ClaimsIdentity(IdentityConfig.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userIdentity.Id));
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
string AccessToken = IdentityConfig.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
return AccessToken;
}
return "failed";
}
[HttpGet]
[Authorize]
[ActionName("Logout")]
public String Logout()
{
var owinContext = HttpContext.Current.GetOwinContext();
owinContext.Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie, DefaultAuthenticationTypes.ExternalBearer);
return "OK";
}
The Authenticate-method works well. My webapp gets a token from the request which I can set as an Authorization-header (e.g. in $http for angular apps). Subsequent calls to a [Authorize]-annotated function will return correctly.
However, if I call Logout, it will return the "OK" string correctly, but doesn't invalidate the token. If I call a Authorize-method after calling Logout, I still get a correct value and not the expected 401 - Unauthorized.
I've seen this post: ASP.Net Identity Logout and tried the Signout without parameters. That doesn't work either.
HttpContext doesn't have the GetOwinContext. It's in HttpContext.Current in my case. Am I doing something wrong?
Why is my Logout Method not working?
It seems like I got the basic concept of (bearer) tokens wrong and that's why it is not working. I leave this here in case somebody stumbles over the same problem:
Tokens can't be revoked or invalidated - at least not with ASP.NET Identity 2.0. The SignOut does not
work for those kinds of authentications.
A solution for this are so called refresh tokens. There's currently no default implementation in Identity 2.0 or OWIN. But I found two blog posts with a solution:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
http://leastprivilege.com/2013/11/15/adding-refresh-tokens-to-a-web-api-v2-authorization-server/