Just like in this question, I want to sign out another user via updating the Security Stamp. However it doesn't work when testing on my local machine. I suspect the problem might be with the order of commands I'm using to reset a user and persisting the different properties to the db.
That's my Startup.Auth
public partial class Startup
{
public static TimeSpan expireTimeSpan = TimeSpan.FromHours(24);
public static IDataProtectionProvider DataProtectionProvider { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
ExpireTimeSpan = expireTimeSpan,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
DataProtectionProvider = app.GetDataProtectionProvider();
}
}
And this is a controller method that allows changing another users email == username. On changing the email, the user is supposed to be logged out and not have a valid password anymore.
public async Task<IHttpActionResult> UpdateUser(string id, ApplicationUser newUser)
{
var user = await _userManager.FindByIdAsync(id);
if (user == null) ...
IdentityResult result;
user.name = newUser.name;
user.roles = newUser.roles;
// sign out user and invalidate password
if (user.email != newUser.email)
{
user.email = newUser.email;
user.PasswordHash = null;
result = await _userManager.UpdateSecurityStampAsync(user.Id);
if (!result.Succeeded) throw new Exception("Security Stamp not updated.");
await _account.SendPasswordEmail(user);
}
result = await _userManager.UpdateAsync(user);
if (!result.Succeeded)
return GetErrorResult(result);
return Ok();
}
I have tried persisting the user first, then generating a new SecurityStamp, but that didn't work either.
Any ideas what could be wrong?
Thanks!
Apparently you didn't read the answer in the question you linked to clearly.
// important to register UserManager creation delegate. Won't work without it
app.CreatePerOwinContext(UserManager.Create);
This is further explained here:
https://aspnetidentity.codeplex.com/workitem/2209
However, you should be aware of this bug:
ExpireTimeSpan ignored after regenerateIdentity / validateInterval duration in MVC Identity (2.0.1)
This will be fixed in the forthcoming Identity 2.2/Owin 3.0 release, but is not yet final.
https://aspnetidentity.codeplex.com/workitem/2347
Also see this blog article:
http://tech.trailmax.info/2014/08/cookieauthenticationprovider-and-user-session-invalidation-on-change-of-securitystamp/
Related
I'm setting up a new instance of IdentityServer as an identity provider. While logging in, I want to set some extra, custom claims on my user object. Right now, I'm using the following code:
[HttpPost]
public async Task<IActionResult> ExecuteLogin(string returnUrl, string loginId)
{
TestUser user = Config.GetUsers().Find(x => x.SubjectId == loginId);
if (user != null)
{
var identityServerUser = new IdentityServerUser(user.SubjectId)
{
AdditionalClaims = user.Claims
};
await HttpContext.SignInAsync(identityServerUser);
return Redirect(returnUrl);
}
else
{
return Redirect("Login");
}
}
I expected the AdditionalClaims to show up on the User.Claims object on the receiving application, which I use as following:
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
var claims = User.Claims;
return View(claims);
}
}
However, in the view only the standard claims are visible. Not my additional claims.
In the setup of IdentityServer I specified a client with access to the scope these claims are in, and an IdentityResource with the claimtypes specified in the TestUser. On the receiving application, I specified I want to receive that scope.
What makes that my claims are not visible on the receiving application?
It is not said what type of authentication you are using, but I suppose you want to add the claims to the access_token from where they can be read by on the API.
AdditionalClaims on IdentityServerUser are only added to the cookie in your client.
What you have to do is to create a profile service (https://docs.identityserver.io/en/latest/reference/profileservice.html).
At the simplest it will be something like this:
public class ProfileService : IProfileService
{
private UserService _userService;
public ProfileService(UserService userService)
{
_userService = userService;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = await _userService.GetUserByIdAsync(context.Subject.GetSubjectId());
context.IssuedClaims.AddRange(user.Claims);
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
var user = await _userService.GetUserByIdAsync(context.Subject.GetSubjectId());
context.IsActive = user.IsActive;
return Task.FromResult(0);
}
}
And register it in the Startup.cs:
services.AddIdentityServer()
.AddProfileService<ProfileService>();
These can then be read from the access_token on the API side (if that's what you wanted as it is not clear from the question):
var user = User.Identity as ClaimsIdentity;
var claims = user.Claims;
You need to explicitly map those extra claims in your client, using code like:
options.ClaimActions.MapUniqueJsonKey("website", "website");
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
options.ClaimActions.MapUniqueJsonKey("birthdate", "birthdate");
There is also this option you can set:
options.GetClaimsFromUserInfoEndpoint = true;
I've set up a simple login page to login my user when he clicks the login button. The user gets assigned roles upon the login. To test if it works out I've done the following code for login:
[HttpPost]
[ActionName("Login")]
public ActionResult Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
string userName = model.Username;
string[] userRoles = new string[5];
userRoles[0] = "Administrator";
ClaimsIdentity identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userName));
// userRoles.ToList().ForEach((role) => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
identity.AddClaim(new Claim(ClaimTypes.Role, userRoles[0]));
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
AuthenticationManager.SignIn(identity);
return RedirectToAction("Success");
}
else
{
return View("Login",model);
}
}
And I've added a Authorize attribute to my MVC action, just to see if the user will really be able to access it after the login... Here's how I've done it:
[Authorize(Roles="Administrator")]
public ActionResult Register()
{
var model = new UserRegistrationViewModel();
var countries = Connection.ctx.Countries.OrderBy(x => x.CountryName).ToList();
model.Countries = new SelectList(countries, "CountryId", "CountryName");
return View(model);
}
But for some reason when I try to access like following:
mywebsite.com/user/register
It shows me:
HTTP Error 401.0 - Unauthorized
You do not have permission to view this directory or page.
What could it be ?
Edit:
Here is the snapshot of claims and identities after the user logs in:
And 2nd one:
Could you ensure that you have Cookie middleware? For example,
Startup.cs
[assembly: OwinStartup(typeof(YourApplicationName.Startup))]
namespace YourApplicationName
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
}
}
Using Cookie Middleware without ASP.NET Core Identity
I'd say you are getting the 401 because your user does not have the "Administrator" role. Check your user (identity) on the subsequent request(s), I am not sure that the Roles persist in the cookie - you may need to find a way to persist the roles yourself.
I'm working on an MVC 6 application that does not use Entity or Identity. Instead, I'm using Dapper. I have a controller that accepts a POST request and uses Dapper to check the database to see if the users name / password match. All I'd like to do is store the users name and whether they're logged in or not so I can make that check on subsequent pages.
I looked around and it seems like using Cookie based authentication should allow me to do what I want. Here's the relevant code in my Startup.cs file:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = "/account/login",
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
Here's what the relevant code in my controllers login action looks like:
var user = _repo.FindByLogin(model.VendorId, model.Password);
if (user != null) {
var claims = new List < Claim > {
new Claim("VendorId", user.VendorId),
new Claim("Name", "john")
};
var id = new ClaimsIdentity(claims, "local", "name", "role");
await HttpContext.Authentication.SignInAsync("Cookies", new ClaimsPrincipal(id));
var l = ClaimsIdentity.DefaultNameClaimType;
return RedirectToAction("Index", "PurchaseOrders");
}
The above code seems to work in that a cookie is being saved, but I'm not sure how I would go about getting the user information back out of the cookie (or even how to retrieve the cookie on subsequent requests in the first place).
In my mind I'm imagining being able to do something like: var user = (User)HttpContext.Request.Cookies.Get(????), but I'm not sure if that's practical or not.
You can get the user data back by using the ClaimsPrincipal.FindFirstValue(xxx)
here is my example class which can be used in Controller/Views to get the current user information
public class GlobalSettings : IGlobalSettings
{
private IHttpContextAccessor _accessor;
public GlobalSettings(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public string RefNo
{
get
{
return GetValue(_accessor.HttpContext.User, "employeeid");
}
}
public string SAMAccount
{
get
{
return GetValue(_accessor.HttpContext.User, ClaimTypes.WindowsAccountName);
}
}
public string UserName
{
get
{
return GetValue(_accessor.HttpContext.User, ClaimTypes.Name);
}
}
public string Role
{
get
{
return GetValue(_accessor.HttpContext.User, ClaimTypes.Role);
}
}
private string GetValue(ClaimsPrincipal principal, string key)
{
if (principal == null)
return string.Empty;
return principal.FindFirstValue(key);
}
}
Example Usage in controller after DI:
var currentUser = GlobalSettings.SAMAccount;
Please note that you need to inject HttpContextAccessor in ConfigureServices method in Startup.cs
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Following the official documentation (https://github.com/rustd/AspnetIdentitySample) and NuGet package, I'm having issues with logging in after a password reset for my MVC5 application. It seems as though Entity Framework doesn't refresh its context in the process, it's only after I restart my application that I can login with the correct credentials.
As far as I can work out, I've done everything that the code samples have done as well. Only I have much more code and settings (e.g. Unity).
This is the problem area:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
try
{
if (ModelState.IsValid)
{
ApplicationUser user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
await this.SignInAsync(user, false);
return RedirectToLocal(returnUrl);
}
else
{
model.State = ViewModelState.Error;
model.Messages = new List<string>() { "No access buddy!" };
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
catch (Exception ex)
{
throw;
}
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
This part works perfectly when I log on for the first time. However, after I have reset my password, logging in with the new credentials isn't possible (it still takes the old version).
Here is my configuration:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
#region Constructor
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
this.UserTokenProvider = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();
}
#endregion Constructor
#region Methods
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
ApplicationUserManager manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<SecurityDbContext>()));
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
#endregion Methods
}
This is what I've configured during Startup:
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(SecurityDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
Ultimately, after a few screens, here is where the user ultimately ends up to create a new password:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationUser user = await UserManager.FindByEmailAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
IdentityResult result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
else
{
AddErrors(result);
return View();
}
}
No errors here either, it stores the new hashed value and security stamp in the database. I'm thinking of some caching, cookies or dbContext that isn't refreshed at the time the password is reset.
Does anyone have any ideas?
Ok so I have finally found the reason for this odd behavior. I had the following DbConfiguration:
public class Configuration : DbConfiguration
{
public Configuration()
{
CacheTransactionHandler transactionHandler = new CacheTransactionHandler(new InMemoryCache());
this.AddInterceptor(transactionHandler);
Loaded += (sender, args) =>
{
args.ReplaceService<DbProviderServices>((s, _) => new CachingProviderServices(s, transactionHandler));
};
}
}
Commenting out the callback did the trick, which sounds logical as I replaced the standard DbProviderServices with second-level caching (as provided by https://efcache.codeplex.com/)
Update:
It's not necessary to entirely remove the second-level caching. Instead, by adding a caching provider, I can choose which tables to cache (and for how long). Here is the updated code:
public class Configuration : DbConfiguration
{
public Configuration()
{
CacheTransactionHandler transactionHandler = new CacheTransactionHandler(new InMemoryCache());
this.AddInterceptor(transactionHandler);
MyCachingPolicy cachingPolicy = new MyCachingPolicy();
Loaded += (sender, args) =>
{
args.ReplaceService<DbProviderServices>((s, _) => new CachingProviderServices(s, transactionHandler, cachingPolicy));
};
}
}
internal class MyCachingPolicy : CachingPolicy
{
#region Constructor
internal MyCachingPolicy()
{
this.NonCachableTables = new List<string>()
{
"AspNetUsers",
"Resource",
"Task",
"Appointment"
};
}
#endregion Constructor
#region Properties
private List<string> NonCachableTables { get; set; }
#endregion Properties
#region Methods
#endregion Methods
protected override bool CanBeCached(ReadOnlyCollection<EntitySetBase> affectedEntitySets, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
{
return !affectedEntitySets.Select(e => e.Table ?? e.Name).Any(tableName => this.NonCachableTables.Contains(tableName));
}
protected override void GetCacheableRows(ReadOnlyCollection<EntitySetBase> affectedEntitySets, out int minCacheableRows, out int maxCacheableRows)
{
base.GetCacheableRows(affectedEntitySets, out minCacheableRows, out maxCacheableRows);
}
protected override void GetExpirationTimeout(ReadOnlyCollection<EntitySetBase> affectedEntitySets, out TimeSpan slidingExpiration, out DateTimeOffset absoluteExpiration)
{
base.GetExpirationTimeout(affectedEntitySets, out slidingExpiration, out absoluteExpiration);
}
}
I have Global Filter as
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
My Signin Code is
[AllowAnonymous,HttpPost]
public async Task<ActionResult> Login(UsersViewModel usermodel)
{
if (ModelState.IsValid)
{
Mapper.CreateMap<UsersViewModel, Users>();
Users model = Mapper.Map<Users>(usermodel);
Users result = await UserManager.FindAsync(model.UserName, usermodel.Password);
if (result != null)
{
await SignInAsync(result, true);
return RedirectToAction("Success", "Home");
}
ModelState.AddModelError("Error", "Incorrect username and/or password");
}
return RedirectToAction("Index", "Home");
}
private async Task SignInAsync(Users user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{
IsPersistent = isPersistent
}, identity);
}
Now when I am signing in then its redirecting to /Home/Success. Its alright I saw the cookies its also creating the authentication cookie. However when I am trying to access /Account/AddRole which isnt marked as allowannonymous. In spite I have already authenticated . Its still redirecting me to /Account/Login . Which shouldn't happen . However my Authentication Startup Class for Owin is :
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(() => new RemsContext());
app.CreatePerOwinContext<RemsContext>(() => new RemsContext());
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieName = "Authcookie",
CookieSecure = CookieSecureOption.Always,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
Why its not authentication working inspite it sets the authentication cookie for Asp.net ? From Identity Framework . I m currently using Identityframework beta 2.0.1 . can someone guide why authorize in global isnt working as expected in spite I have already logged in and the authentication cookie has been set ?