I'm building C# (.net core 2.1) Razor Page site. Login mechanism will work based on documentation from here - so token in cookies (if I understand it correctly): https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-2.1
Let's say I have session set to 3 min, and if user is actively using site, I'm resetting this value. If he is not, 30 seconds before token expires I want to show him message like "Do something or You will be logged out".
My problem - I can't find way to get this "expires" time.
This is how I validate and store data:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
int iCookieLifeTime = 10;
if (ModelState.IsValid)
{
var user = await AuthenticateUser(Input.UserName, Input.License, Input.Password, Input.KeepLogged);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim("Password", user.Password ) )
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(iCookieLifeTime)
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
Now, how can I call AuthenticationProperties to get/set data from ExpiresUtc on all my pages? Let's say that setting is working, because I defined parameter AllowRefresh = true. But getting is something I don't know how to do.
In ideal scenario, I'd like to get it on partial pages like I get my authentication data:
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor;
#if (HttpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
(...)
Related
Using IIS Express on my local machine, I'm able to run the IdentityServer4 QuickStart UI project and successfully sign in. However, once it is deployed to production, I'm unable to get it to work.
On the Application Pool for the site, I have a domain account setup (with just about every permission possible granted). I have tried every variation of having "anonymous authentication" toggled. I've gone as far as recreating the entire application from scratch in multiple different ways (no-SSL, only-SSL, fully open CORS, all security policies disabled), and even the most basic version of the application seems to suffer from the exact same issue.
After slapping some logging on the application, I can see that I'm grabbing the Subject ID and name from AD just fine.
Here's the ProcessWindowsLoginAsync method, with only minimal logging changes.
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
var props = new AuthenticationProperties
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
var sub = wp.FindFirst(ClaimTypes.PrimarySid).Value;
id.AddClaim(new Claim(JwtClaimTypes.Subject, sub));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
_logger.LogInformation("Assigning claims. Subject {#Subject}. Name {#Name}", sub, wp.Identity.Name);
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi!.Groups!.Translate(typeof(NTAccount));
var roles = groups!.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
The above code spits out something akin to (with identifying information stripped):
Assigning claims. Subject S-0-0-00-0000000000-0000000000-0000000000-00000. Name DOMAIN\NAME
Once the above has executed, the external callback method is called and it immediately throws an exception:
[HttpGet]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
_logger.LogInformation("We were not successfully able to sign in. Failure: {#Failure}. None: {#None}", result?.Failure, result?.None);
if (result?.Failure != null)
throw result.Failure;
throw new Exception("External authentication error");
}
if (_logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {#claims}", externalClaims);
}
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
if (user == null)
user = AutoProvisionUser(provider, providerUserId, claims);
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
var issuer = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(issuer, localSignInProps);
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId));
if (context != null)
if (await _clientStore.IsPkceClientAsync(context.ClientId))
return this.LoadingPage("Redirect", returnUrl);
return Redirect(returnUrl);
}
From the logs, I can tell that it's immediately failing after attempting to authenticate. There's no other errors, but a few interesting logs of note (in order):
Performing protect operation to key {xxxxxxxx-xxxx-xxxx-xxxx-b7e4d6dd250a} with purposes ('C:\websites\identity.ourdomain.com', 'Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware', 'idsrv.external', 'v2').
AuthenticationScheme: idsrv.external signed in.
Executing RedirectResult, redirecting to /External/Callback
Executing action method IdentityServer4.Quickstart.UI.ExternalController.Callback (Idsvr.Api) - Validation state: Valid
AuthenticationScheme: idsrv.external was not authenticated.
(Exception)
One of the possible root cause is that the callback cookie doesn't set properly.
Try to capture the network traffic, and check if idsrv.external cookie has been set correctly during Challenge.
In my case, setting cookie failed because SameSite=None is there without Secure=true.
I'm currently trying to update the user's email/username from a mobile app to a Web API project. I'm currently using oauth and token authentication. When updating the identity user, the user becomes unauthenticated because the username and access token are no longer valid. From what I have read, I must update the identity claims. This is what I have tried so far:
var identity = new ClaimsIdentity(User.Identity);
if (result)
{
var identityUser = await UserManager.FindByNameAsync(User.Identity.Name);
identityUser.Email = AntiXssEncoder.HtmlEncode(value.Email, true);
identityUser.UserName = AntiXssEncoder.HtmlEncode(value.Email, true);
var identityResult = await UserManager.UpdateAsync(identityUser);
if(identityResult.Succeeded)
{
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
await UserManager.RemoveClaimAsync(identityUser.Id, identity.FindFirst(ClaimTypes.Name));
await UserManager.AddClaimAsync(identityUser.Id, new Claim(ClaimTypes.Name, value.Email));
identity.RemoveClaim(identity.FindFirst(ClaimTypes.Name));
identity.AddClaim(new Claim(ClaimTypes.Name, value.Email));
authenticationManager.AuthenticationResponseGrant =
new AuthenticationResponseGrant(
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = false });
}
return Ok();
}
However, it still shows the previous email when using User.Identity.Name and the claims for the user within the authenticationManager have not been updated either. I'm not sure what else to do as there isn't much documentation on this for Web API. Any help is greatly appreciated.
Main problem is that claim which represents user's name is not updated in ClaimsIdentity you are using in the last step.
The easiest way to perform the update is to use SignInManager<TUser, TKey>.SignIn method
signInManager.SignIn(identityUser, isPersistent: false, rememberBrowser: false);
This is also an ASP.NET Identity idiomatic way since it is using associated IClaimsIdentityFactory to create claims for new identities.
Complete example
static async Task<IdentityResult> UpdateEmailAsync<TUser>(
IPrincipal principal,
UserManager<TUser, string> userManager,
SignInManager<TUser, string> signInManager,
string newEmail
)
where TUser : class, IUser<string>
{
string userId = principal.Identity.GetUserId();
IdentityResult result = await userManager.SetEmailAsync(userId, newEmail);
if (result.Succeeded)
{
// automatically confirm user's email
string confirmationToken = await userManager.GenerateEmailConfirmationTokenAsync(userId);
result = await userManager.ConfirmEmailAsync(userId, confirmationToken);
if (result.Succeeded)
{
TUser user = await userManager.FindByIdAsync(userId);
if (user != null)
{
// update username
user.UserName = newEmail;
await userManager.UpdateAsync(user);
// creates new identity with updated user's name
await signInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
// succeded
return result;
}
}
// failed
return result;
}
Then you can just call it from your code
string newEmail = AntiXssEncoder.HtmlEncode(value.Email, true);
IdentityResult result = await UpdateEmailAsync(identityUser, UserManager, SignInManager, newEmail);
if (result.Succeeded)
{
return Ok();
}
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
When the user is logging in, I need to remember him, because only logged in users should be able to reach /api/prenews (for example). This is how I'm trying to do it. (The userName variable is a string, which stores the current username if the log in was successful.)
IList<Claim> claimCollection = new List<Claim>
{
new Claim("username",userName)
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claimCollection);
HttpContext.Current.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, claimsIdentity);
I implemented the PrenewsController.cs like this:
public class PrenewsController : ApiController
{
/*
...
*/
public IHttpActionResult GetPrenews()
{
string currentUser = HttpContext.Current.GetOwinContext().Authentication.User.Claims.FirstOrDefault(k => k.Type == "username").Value;
if(currentUser!=null)//the user is logged in
{/*...*/}
else //the user is not logged in
{/*...*/}
}
}
But HttpContext.Current.GetOwinContext().Authentication.User.Claims.FirstOrDefault(k => k.Type == "username") is always null. What am I doing wrong?
You should be able to get username from User.Identity property no need to add it to claims, also I guess your claim is null cause IsPersistent property is set to false in your example, try setting it to true and it should work.
EDITED
Try this:
var user = userManager.Find(userName, password);
var userIdentity = await userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
userIdentity.AddClaim(new Claim("usename", "johndoe"));
HttpContext.Current.GetOwinContext().Authentication.SignIn(new AuthenticationProperties() { IsPersistent = persistCookie }, userIdentity)
this method definetely worked for me.. I hope it helps.
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);
}