Protecting an endpoint with AD and OWIN - c#

I am currently trying to authenticate users for a web app with our domain AD and so far I have been able to successfully login.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var authService = new ADAuthenticationService(AuthenticationManager);
var authenticationResult = authService.SignIn(model.Email, model.Password);
if (authenticationResult.IsSuccess)
{
// To home/index since no returnUrl is actually being captured for now
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError("", authenticationResult.ErrorMessage);
return View(model);
}
My auth service creates a claim identity and signs the user in. Next, I want to secure my web api endpoints to allow only logged in clients to send requests to it. Following an example I have the following:
[HttpPost]
public ActionResult Authorize()
{
var claims = new ClaimsPrincipal(User).Claims.ToArray();
var identity = new ClaimsIdentity(claims, "Bearer");
AuthenticationManager.SignIn(identity);
return new EmptyResult();
}
However, when I actually try to go to the endpoint it tells me I am unauthorized. In the request headers I have "Authorization: Bearer token here".
EDIT:
In Startup.Auth.cs I have auth configured as such
static Startup()
{
PublicClientId = "web";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/Account/Authorize"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = ADAuthentication.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(validateInterval: TimeSpan.FromMinutes(20),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
CookieName = "MyAppAuthenticationType",
CookieHttpOnly = true,
ExpireTimeSpan = TimeSpan.FromHours(1),
});
app.UseOAuthBearerTokens(OAuthOptions);
}
EDIT 2:
ADAuthenticationService.cs
public ADAuthenticationService(IAuthenticationManager authenticationManager)
{
this.authenticationManager = authenticationManager;
}
public AuthenticationResult SignIn(String username, String password)
{
PrincipalContext principalContext = new PrincipalContext(authenticationType);
bool isAuthenticated = false;
UserPrincipal userPrincipal = null;
try
{
userPrincipal = UserPrincipal.FindByIdentity(principalContext, username);
if (userPrincipal != null)
{
isAuthenticated = principalContext.ValidateCredentials(username, password, ContextOptions.Negotiate);
}
}
catch (Exception exception)
{
System.Diagnostics.Debug.WriteLine(exception);
return new AuthenticationResult("Username or Password is not correct");
}
/* some other invalid login results here */
var identity = CreateIdentity(userPrincipal);
authenticationManager.SignOut(ADAuthentication.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);
return new AuthenticationResult();
}
private ClaimsIdentity CreateIdentity(UserPrincipal userPrincipal)
{
var identity = new ClaimsIdentity(ADAuthentication.ApplicationCookie, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Active Directory"));
identity.AddClaim(new Claim(ClaimTypes.Name, userPrincipal.SamAccountName));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userPrincipal.SamAccountName));
if (!String.IsNullOrEmpty(userPrincipal.EmailAddress))
{
identity.AddClaim(new Claim(ClaimTypes.Email, userPrincipal.EmailAddress));
}
var groups = userPrincipal.GetAuthorizationGroups();
foreach (var #group in groups)
{
identity.AddClaim(new Claim(ClaimTypes.Role, #group.Name));
}
return identity;
}

Related

C# JWT token persist claims after update?

I want to update the user's claims using HttpContext.User instance, but after updating the claims they only stay within the scope of the current request. I need to make it persist for the upcoming requests as well, please help me out with this.
Please find my code below. In the POST method I update the claim and next time when the GET method is hit, I am trying to get the updated value but I get the old value.
[Route("login")]
public class LoginController : Controller
{
private readonly IList<User> users = new List<User>
{
new User { UserName = "admin", Password = "1234", Role="Administrator"},
new User { UserName = "user", Password ="1234", Role="User"}
};
private IConfiguration _config;
public LoginController(IConfiguration config)
{
this._config = config;
}
[HttpGet("Enter")]
public IActionResult Login([FromQuery]string username, [FromQuery]string password)
{
User login = new User();
login.UserName = username;
login.Password = password;
IActionResult response = Unauthorized();
var user = AuthenticateUser(login);
if(user != null)
{
var tokenStr = GenerateJSONWebToken(user);
response = Ok(new { token = tokenStr });
}
return response;
}
private string GenerateJSONWebToken(User userinfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim("username", userinfo.UserName),
new Claim(ClaimTypes.Role, userinfo.Role),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials
);
var encodettoken = new JwtSecurityTokenHandler().WriteToken(token);
return encodettoken;
}
[Authorize(Roles = "Administrator")]
[Authorize]
[HttpPost("Post")]
public string Post()
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
IList<Claim> claim = identity.Claims.ToList();
var username = claim[0].Value;
return "Welcome To " + username;
// i update claim here
var identityClaims = (ClaimsIdentity)User.Identity;
var username = identityClaims.FindFirst("username");
if (username != null)
identityClaims.RemoveClaim(username);
identityClaims.AddClaim(new Claim("username", "sample username"));
}
[Authorize(Roles = "Administrator, User")]
[HttpGet("GetValue")]
public ActionResult<IEnumerable<string>> Get()
{
// in the next get request i try to access the claim, but it does not have the updated value
// instead it has the old value
// here i have to persist the value
var identityClaims = (ClaimsIdentity)User.Identity;
var username = identityClaims.FindFirst("username");
return new string[] { "Value1", "Value2", "Value3" };
}
private User AuthenticateUser(User login)
{
User entity = null;
if (users.Where(x=>x.UserName == login.UserName && x.Password == login.Password).ToList().Count() > 0)
{
entity = users.Where(x => x.UserName == login.UserName && x.Password == login.Password).FirstOrDefault();
}
return entity;
}
}

Authentication and Authorize with Jwt Bearer Claims/Roles

I'm trying to create an authorize with jwt bearer, but I cant do it on my application, when I do with postman it happening what I want, but on my app don't...
Service.ts (Angular 8)
getAll(): Observable<...[]> {
return this.httpClient.get<...[]>(environment.url + "api",
{ headers: {'Authorization' : 'Bearer ' + token });
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var symetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"]));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = symetricSecurityKey
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
AuthController.cs
[HttpPost]
[Route("login")]
public async Task<AccountModel> Login([FromBody] AccountModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser();
var signInResultado = new Microsoft.AspNetCore.Identity.SignInResult();
Task.Run(async () =>
{
user = await _userManager.FindByEmailAsync(model.Email);
}).Wait();
Task.Run(async () =>
{
await _userManager.CheckPasswordAsync(user, model.Password);
}).Wait();
Task.Run(async () =>
{
signInResultado = await _signInManager.PasswordSignInAsync(
user.UserName,
model.Password,
isPersistent: false,
lockoutOnFailure: false);
}).Wait();
if (signInResultado.Succeeded)
{
var appUser = _userManager.Users.FirstOrDefault(u => u.Id == user.Id);
var claims = await GetValidClaims(appUser);
var accountModel = new AccountModel(user, _roleManager);
accountModel.Token = GenerateJwtToken(appUser, claims);
return accountModel;
}
}
return model;
}
private string GenerateJwtToken(ApplicationUser user, List<Claim> claims)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["JwtKey"]);
var tokens = new JwtSecurityToken(
claims: claims,
expires: DateTime.UtcNow.AddDays(1),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
);
return new JwtSecurityTokenHandler().WriteToken(tokens);
}
private async Task<List<Claim>> GetValidClaims(ApplicationUser user)
{
IdentityOptions _options = new IdentityOptions();
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(_options.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
new Claim(_options.ClaimsIdentity.UserNameClaimType, user.UserName)
};
var userClaims = await _userManager.GetClaimsAsync(user);
var userRoles = await _userManager.GetRolesAsync(user);
claims.AddRange(userClaims);
foreach (var userRole in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, userRole));
var role = await _roleManager.FindByNameAsync(userRole);
if (role != null)
{
var roleClaims = await _roleManager.GetClaimsAsync(role);
foreach (Claim roleClaim in roleClaims)
{
claims.Add(roleClaim);
}
}
}
return claims;
}
Any method with Role = Admin
[HttpGet]
[Authorize(Roles = "Admin")]
public ActionResult<IEnumerable<RoleModel>> Get()
Postman - Login (Create Token)
Postman - Get any method with Role = Admin
When I did on my application, it redirected to Identity/Account/Login

C# Web API update claims

I make asp.net web api project.
Client program is winform use HttpClient.
I assign user language to claims when user log in.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
.......
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("UserId", context.UserName));
identity.AddClaim(new Claim("role", "user"));
identity.AddClaim(new Claim("Language", forms["language"]));
context.Validated(identity);
......
}
I need to change language of claims runtime.
So i make change language api.
[HttpGet]
[APIAuthorize]
public HttpResponseMessage ChangeLanguage(Language language)
{
User.AddUpdateClaim("Language", language.ToString());
return Request.CreateResponse<IResult>(HttpStatusCode.OK, "OK");
}
and make ClaimsIdentity extension.
public static class ClaimsIdentityExtensions
{
public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value)
{
var identity = new ClaimsIdentity(currentPrincipal.Identity);
// check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
identity.RemoveClaim(existingClaim);
// add new claim
identity.AddClaim(new Claim(key, value));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
HttpContext.Current.GetOwinContext().Authentication.SignOut(DefaultAuthenticationTypes.ExternalBearer);
HttpContext.Current.GetOwinContext().Authentication.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);
ClaimsPrincipal principal = Thread.CurrentPrincipal as ClaimsPrincipal;
if (principal != null)
{
identity = principal.Identities.ElementAt(0);
var old = identity.FindFirst(key);
if (old != null)
identity.RemoveClaim(old);
identity.AddClaim(new Claim(key, value));
}
}
public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;
var claim = identity.Claims.First(c => c.Type == key);
return claim.Value;
}
}
then client call "ChangeLanguage" api
[HttpGet]
[APIAuthorize]
public HttpResponseMessage ChangeLanguage(Language language)
{
User.AddUpdateClaim("Language", language.ToString());
return Request.CreateResponse<IResult>(HttpStatusCode.OK, "OK");
}
but constructor of controller called next time
claims not updated.
public MyController()
{
var user = this.User.Identity as ClaimsIdentity;
if (user.IsAuthenticated)
{
// language not changed
var language = User.GetClaimValue("Language");
}
}
How can i change claims?
Sorry my english is terrible.
Thank you

UserManager.FindAsync returns null after password reset

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);
}
}

HttpContext.Current.User.Identity.Name is null after setting custom principal in GrantResourceOwnerCredentials in my owin authorization provider

Here is my GrantResourceOwnerCredentials method:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
AccountModels.UserProfile user = ApplicationUserManager.UserLogin(new AccountModels.LoginModel { UserName = context.UserName , Password = context.Password, RememberMe= false });
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
else
{
if (user.IsLoggedIn = false)
{
context.SetError("invalid_grant", "The user is no longer active. Please contact support for account activation");
return;
}
else
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, user.UserRole));
var roles = new string[] {user.UserRole};
AuthenticationProperties properties = CreateProperties(user.UserName, roles, user.IsEmilConfirmed);
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
context.Validated(ticket);
MyCustomPrincipal newUser = new MyCustomPrincipal(user.UserName);
newUser.Id = user.UserID;
newUser.Email = user.UserName;
newUser.Role = user.UserRole;
SetPrincipal(newUser);
context.Request.Context.Authentication.SignIn(identity);
}
}
}
I had to implement a custom login logic to support my old database schema and after login i wanted to set the just logged in user in httpcontext.current.user.
Here is my SetPrincipal method:
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
After authorization whenever i call HttpContext.Current.User.Identity.Name in a controller i get null value. What am i missing here? Can anyone give me any idea?
Try using the User object in your controller:
var principal = User as ClaimsPrincipal;
Side note: call to SetPrincipal is not necessary in the GrantResourceOwnerCredentials if you are using OWIN hosting.

Categories