Using Identity with token and cookie authentication - c#

Im trying to setup Token authentication with cookie authentication on same time in my application.
I created a MVC project in asp.net core 2.0, with individual user accounts to auth. Setup roles to the users too.
If i follow this tutorial of Shawn Wildermuth Two-AuthorizationSchemes-in-ASP-NET-Core-2
Everything works fine to get the Token of the registered user. But if i use the Role attribute on authorize [Authorize(Roles="Admin")] im getting a 403 response.
I think that is because the Token is not receiving the Role on auth.
How to setup this? Is any way to pass the Roles on the Token process?
To generate the token he is using this piece of code:
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> GenerateToken([FromBody] LoginViewModel model) { if (ModelState.IsValid) {
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_config["Tokens:Issuer"],
_config["Tokens:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
} }
return BadRequest("Could not create token"); }
You guys have any idea?
Thanks

If you add the following using and code, that should help.
using System.Security.Claims;
...
var userRoles = await _userManager.GetRolesAsync(user);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
}.Union(userRoles.Select(m => new Claim(ClaimTypes.Role, m)));
You can see the Union that adds the roles in with the type of ClaimTypes.Role, this will enable them to be used in the AuthorizeAttribute
HTH

Related

How do I validate a username and password in Azure AD?

I want an example code to allow users to login with their username and password in Azure AD. After successfully logging in, I want to get an Access Token
At the moment I have no connection with Azure AD, I hard-coded a user.
// POST api/values
[HttpPost, Route("login")]
public IActionResult Login([FromBody] LoginModel user)
{
if (user == null)
{
return BadRequest("Invalid client request");
}
if (user.UserName == "JO3434" && user.Password == "defDDMKJM")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSMKLJMKey#345"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName)
};
var token = new JwtSecurityToken(
audience: "http://site.azurewebsites.net",
issuer: "http://site.azurewebsites.net",
claims: claims,
expires: DateTime.Now.AddMinutes(60),
signingCredentials: signinCredentials
);
var results = new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
};
return Ok(results);
}
else
{
return Unauthorized();
}
}
}
Without connection with Azure AD, you could not validate the user, also I think it is unnecessary to do that, if you get the token with AcquireTokenByUsernamePassword methond, it essentially uses the Azure AD ROPC flow, it will validate the user automatically for you, if the user is invalidated, it will give an error Error validating credentials due to invalid username or password.

Check SecurityStamp every request using Access Token asp.net identiy

I'm using asp.net identiy to protect my api,
I use the following function to create Access Tokenfor users when they log in
private string GenerateAccessToken(string userName, string role)
{
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, userName));
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
DateTime currentUtc = DateTime.UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365));
string accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
return accessToken;
}
Everything is fine until I perform an account password update, after that I update SecurityStamp
UserManager.UpdateSecurityStampAsync(loggedinUser.Id);
but the problem is that the token can still be used to call my api without any problem. So how do I check SecurityStamp with each request?
You can check SecurityStamp using the JwtBearerEvents configured in startup.cs or program.cs depending of your .Net version.
This is a very simple version of SecurityStamp validation (.Net 6):
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg =>
{
cfg.Events = new JwtBearerEvents
{
OnTokenValidated = async (ctx) =>
{
var signInManager = ctx.HttpContext.RequestServices
.GetRequiredService<SignInManager<ApplicationUser>>();
var user = await signInManager.ValidateSecurityStampAsync(ctx.Principal);
if (user == null)
{
ctx.Fail("Invalid Security Stamp");
}
}
};
// more code...
});
Note:
For this example to work correctly, you need to ensure that you are packaging the SecurityStamp along with the user's claims during token creation, as in the example below.
var identityOptions = _config.Get<ClaimsIdentityOptions>();
claims.Add(new Claim(identityOptions.SecurityStampClaimType, user.SecurityStamp));

ASP.NET Core Website to WebApi authentication using JWT token

I am developing an ASP.NET Core 2.2 Website where users need login and then use it.
The AccountController in my Website calls another ASP.NET Core WebApi (with [AllowAnonymous] attribute) to get the JWT token from username and password.
All controllers except for AccountController within the Website will have [Authorize("Bearer")] attribute to check if the user has been authorized.
My WebApi will have other controllers too which will require [Authorize("Bearer")], so the JWT token will be passed from the Website when making http requests. See below configured Startup.cs > ConfigureServices() method file in WebApi project:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidIssuer = "ZZZZ",
ValidAudience = "ZZZZ",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
});
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
And the Configure() method:
app.UseAuthentication();
ASP.NET Core WebApi - generate JWT token:
JWTToken jwt = new JWTToken();
jwt.Token = "";
jwt.Expires = DateTime.UtcNow.AddMinutes(90);
var claims = new[]
{
new Claim(ClaimTypes.UserData, UserId)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(privateSecretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "ZZZ",
audience: "ZZZ",
claims: claims,
expires: jwt.Expires,
signingCredentials: creds);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
jwt.Token = tokenStr;
return jwt;
I have completed the WebApi method to generate a token and return back a JWT token. But what do I do with that token so that the Authentication/Authorization works in my ASP.NET Core Website.
[HttpPost]
public async Task<IActionResult> Login(LoginModel model)
{
var httpClient = _httpClientFactory.CreateClient(ConstantNames.WebApi);
var response = await httpClient.PostAsJsonAsync($"{ApiArea}/authenticate", model);
if (response.IsSuccessStatusCode)
{
var jwtToken = await response.Content.ReadAsAsync<JWTToken>();
/* --> WHAT DO I DO HERE? <-- */
}
else
{
ModelState.AddModelError("Password", "Invalid password");
model.Password = "";
return View(model);
}
return RedirectToAction("Index", "Home");
}
So to make matters complex, the overview of my project is as such:
ASP.NET Core Website - has login page and other controllers with ajax calls for datatables and Forms for Edit pages which must be authorized
ASP.NET Core WebApi - generated JWT token and has methods for other api calls which must be authorized
How do I tell the Website that if user is not Authorized, then go to my /Account/Login page?
Is this process correct, if not do I still need to add Identity and do this differently for Website?
If your ASP.NET Core Website and ASP.NET Web API are two different websites :
For the WebAPI, the client should always send a request by adding a header of Authorization : Bearer {access_token}. Or register an OnMessageReceivedhandler if you would like send it via cookie/querystring
For the ASP.NET Core Website, the browser should use cookies or JWT as credentials.
I'm not sure how your authentication looks like.
Assuming you choose to use cookies for ASP.NET Core Website, make sure you've set the LoginPath = "/Account/Login";
// the Startup::ConfigureServices of your ASP.NET Core Website
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o => {
o.LoginPath = "/Account/Login";
});
And then as suggested by Camilo Terevinto, you need to sign the user in :
[HttpPost]
public async Task<IActionResult> Login(LoginModel model)
{
var httpClient = _httpClientFactory.CreateClient(ConstantNames.WebApi);
var response = await httpClient.PostAsJsonAsync($"{ApiArea}/authenticate", model);
if (response.IsSuccessStatusCode)
{
var jwtToken = await response.Content.ReadAsAsync<JWTToken>();
var username = ...
var others = ...
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
// add other claims as you want ...
};
var iden= new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(iden);
await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, principal);
return Redirect("/")
}
else
{
ModelState.AddModelError("Password", "Invalid password");
model.Password = "";
return View(model);
}
return RedirectToAction("Index", "Home");
}

How to get user id from IdentityServer in client app when including access token?

I have implemented an authentication service based on IdentityServer3 and a simple MVC client app and a Shopper API secured by the authentication service. I've implemented a IdentityServer custom UserService so that the authentication service authenticates against our existing user data store. My Shopper API expects a userid in the Shopper Get request. Currently the response from authentication service includes the identity token and the access token, but no user id. I tried adding a user_id claim in the AuthenticationResult from my custom UserService.AuthenticateLocalAsync method, but I'm not seeing it in my client app code.
UserService.AuthenticateLocalAsync looks like this:
try
{
var user = new shopper(_dbConnLib, context.UserName, context.Password);
var claims = new List<Claim> { new Claim("user_id", user.shopperid) };
context.AuthenticateResult = new AuthenticateResult(user.shopperid, user.MemberDetail.billToAddress.FirstName, claims);
}
catch(shopperInitFromException ex)
{
context.AuthenticateResult = null; // Indicates username/password failure
}
return Task.FromResult(0);
And my client app SecurityTokenValidated handler looks like this:
SecurityTokenValidated = async n =>
{
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo").ToString());
var userInfo = await userInfoClient.GetAsync(n.ProtocolMessage.AccessToken);
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Type, ui.Value)));
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
//nid.AddClaim(new Claim("user_id", n.ProtocolMessage.UserId));
nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
If I step through that in the debugger, userInfo.Claims always has a count of 0. How can I get back a claim with the unique identifier of the user? Or can I get it from the identity or access token? Or should I just pass the tokens to the Shopper API and let it determine the id from the tokens?
I think I may have the answer. So far, as far as I can tell, the claims I include in the AuthenticateResult constructor in my override of AuthenticateLocalAsync don't seem to go anywhere. But the claims I include in my override of GetProfileDataAsync appear in the token. My GetProfileDataAsync code, which appears to set the claims properly, looks like this:
public override Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = new shopper(_dbConnLib, context.Subject.FindFirst("sub").Value);
var claims = new List<Claim> { new Claim("sub", user.shopperid), new Claim("acr_level", "level 0"), new Claim("amr", "anonymous") };
context.IssuedClaims = claims;
return Task.FromResult(0);
}
My AuthenticateLocalAsync code that sets claims in the AuthenticateResult that I never see in my client app code looks like this:
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
// TODO: Handle AddshopperToBasketException in UserService.AuthenticateLocalAsync
try
{
var user = new shopper(_dbConnLib, context.UserName, context.Password);
var claims = new List<Claim> { new Claim("acr_level", "level 0"), new Claim("amr", "anonymous") };
context.AuthenticateResult = new AuthenticateResult(user.shopperid, user.MemberDetail.billToAddress.FirstName, claims);
}
catch(shopperInitFromException ex)
{
context.AuthenticateResult = null; // Indicates username/password failure
}
return Task.FromResult(0);
}

How to switch from Hybrid flow to ResourceOwner flow with IdentityServer3

I need to upgrade (or downgrade) my Website to using a local login page. I had it all working using the hybrid flow using the following code
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions(){});
And then when the token would come back, it would give me access to complete the authentication logic in asp.net- setting the claims identity, principal, etc.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = async n =>
{
// perform transform, etc..
n.AuthenticationTicket = new AuthenticationTicket(
identity, n.AuthenticationTicket.Properties);
await Task.FromResult(0);
}
}
});
Now, I am going to be collecting the username and password from an MVC action method. I am able to get the access token from the client this way.
[HttpPost]
public ActionResult Login(LoginModel model)
{
var client = new TokenClient(
StsSettings.TokenEndpoint,
ClientId,
Secret);
var x = client.RequestResourceOwnerPasswordAsync(model.UserName, model.Password, "customid openid").Result;
return View(model);
}
But I'm not sure how the easiest way to tell ASP.NET to point to my custom login page instead of an identity server. Would I use forms authentication logic and create some AuthenticationTicket? Also, what is the best way set the ClaimsIdentity (I know how to get the claims back, just need a "hook")
If you want the outcome of the resource owner password flow to be the logged in user, you need to issue the main authentication cookie with the claims you have for that newly authenticated user.
var claims = new Claim[] {
new Claim("name", username),
new Claim("sub", "4848784904"),
new Claim("email", "BrockAllen#gmail.com"),
new Claim("role", "Admin"),
new Claim("role", "Dev"),
};
// "Cookies" is the name of your cookie middleware,
// so change to match what you're actually using in Startup.cs
var ci = new ClaimsIdentity(claims, "Cookies", "name", "role");
Request.GetOwinContext().Authentication.SignIn(ci);
return Redirect("~/Home/Secure");

Categories