I have a .NET 4.6 WebApi 2.0 OWIN App that serves HTTP Requests.
Those requests are authenticated and we are storing certain claims in the RequestContext ClaimsIdentity (UserId, Client Code)
We also have our Controllers, Services and Repository structure.
Our Services are injected into the Controllers via Ninject, and the Repositories into the Services.
When the user performers certain requests we need to check their permissions. The service usually needs to know the: UserId, ClientCode, Groups the user has access to and permissions under each group (Read, Write, etc). This last one is only being stored in a DataBase.
The current flow when executing a request is: obtain the userId and clientCode from the RequestContext claims, use that userid to obtain the Group permissions from the DataBase. Create an object UserCredentials with those properies.
This object is then passed on to the Service instances after they were injected
This is an example:
public PeopleController(IPeopleService peopleService, IUserManagementService userManagementService)
{
var clientCode = RequestContext.GetClientCode();
var userId = RequestContext.GetUserId();
var permissions = userManagementService.GetUserPermissions(userId, clientCode);
}
peopleService.InjectCredentials(new services.Models.UserCredentials()
{
ClientCode = clientCode,
UserId = UserId,
Permissions = UserPermissions
});
_peopleService = peopleService;
}
This doesn't look very elegant to us as we need to repeat that code everywhere and makes the IPeopleService dependant that the user doesn't forget to call the InjectCredentials method.
So my question is, how can I make that UserCredentials class Injectable so it can be passed as a parameter to the IPeopleService constructor?
The main block is that it depends on the HttpRequestContext which is only available on the Controller itself
Thanks
EDIT: I know UserCredentials object can be also be passed as a parameter to each service Method (which is actually better than the InjectCredentials method)
Instead of Injecting the Model,
In your IUserManagementService implementation inject IHttpContextAccessor. Then implement a method to do what you would do in the controller.
Let this method be responsible for fetching user details for you.
public class UserManagementService : IUserManagementService
{
private readonly IHttpContext httpContext;
public UserManagementService (IHttpContext httpContext)
{
this.httpContext = httpContext;
}
public UserCredentials GetUserDetails()
{
new services.Models.UserCredentials()
{
ClientCode = clientCode,
UserId = UserId,
Permissions = UserPermissions
});
}
}
Finally, inject this service in any other service where you need these details.
PeopleController(IPeopleService peopleService, IUserManagementService userManagementService)
I found one way that is possible, but I don't think its recommended due to the use of HttpContext.Current.user
kernel.Bind<UserCredentials>().ToMethod(CreateUserCredentials);
private static UserCredentials CreateUserCredentials(IContext arg)
{
var identity = HttpContext.Current.User as ClaimsPrincipal;
int userId = identity.Identity.GetUserId();
string clientCode = identity.Identity.GetClientCode();
var userManagementService = kernel.Get<IUserManagementService>();
if (userId != 0)
{
var permisssions = userManagementService.GetUserPermissions(userId, userType, clientCode);
}
return new UserCredentials()
{
ClientCode = clientCode,
UserId = userId,
Permissions = userManagementService.GetUserPermissions(userId, clientCode)
};
}
Now the UserCredentials object can be added to constructors as a parameter and will properly intialised.
Related
I am developing an intranet asp.net core web api application. The requirements for authentications are:
REQ1 - when user which is trying to access the website is not in Active Directory's special group (let's name it "commonUsers") it is simply not authorized
REQ2 - when user which is trying to access the website is in Active Directory's group "commonUsers" is is authorized and a web resource is returned
REQ3 - when user which is trying to access the website is in Active Directory's group "superUser", it need to be prompted for his domain password once again (because it tries to access some very restricted resources)
Now, what I have so far:
My service is hosted using http.sys server in order to support windows authentication.
I am using claims transformer middlewere in order to check the user's Active Directory group, let's say something like this:
public class ClaimsTransformer : IClaimsTransformation {
private readonly IAuthorizationService _authorizationService;
public ClaimsTransformer(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
_authorizationService.Authorize(principal as IHmiClaimsPrincipal);
return Task.FromResult(principal);
}}
I have specified a special policies also in my service configuration, for instance something like that:
services.AddAuthorization(options =>
{
options.AddPolicy("TestPolicy", policy =>
policy.RequireClaim(ClaimTypes.Role, "TestUser"));
options.AddPolicy("TestPolicy2", policy =>
policy.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
I am using [Authorize] attribute with specific policy in order to restrict access to specific resources based on policies
Now the question is, how should I satisfy REQ3?
I think I would try to use MVC Filters : https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#authorization-filters
Filters run after all Middleware, but before the Action. This will allow you to control the redirect to credentials page just for specific actions or controllers. Whilst normally this is not the recommended method for authorization, I think it fits your requirements for a hybrid secondary authentication.
public class SuperUserFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Cookies.TryGetValue("SuperUserCookie", out string cookieVal))
{
if (!IsValidCookie(cookieVal))
context.Result = LoginPage(context);
}
else
{
context.Result = LoginPage(context);
}
}
private bool IsValidCookie(string cookieVal)
{
//validate cookie value somehow
// crytpographic hash, store value in session, whatever
return true;
}
private ActionResult LoginPage(AuthorizationFilterContext context)
{
return new RedirectToActionResult("SuperUser", "Login",
new {redirectUrl = context.HttpContext.Request.GetEncodedUrl()});
}
}
Then you create a Login Controller
public class LoginController : Controller
{
[HttpGet]
public IActionResult SuperUser(string redirectUrl)
{
// return a page to enter credentials
// Include redirectUrl as field
}
[HttpPost]
public IActionResult SuperUser(LoginData loginData)
{
// Validate User & Password
Response.Cookies.Append("SuperUserCookie", "SomeValue");
return Redirect(loginData.RedirectUrl);
}
}
Then you can decorate specific actions (or controllers) as required:
public class MyController : Controller
{
[HttpGet]
[SuperUserFilter]
public IActionResult MySensitiveAction()
{
// Do something sensitive
}
}
I'm guessing you are try to implement two step authentication for some of your resource.
To do that you must use multiple authentication scheme and Authorize policies,
but it's difficult because windows authentication is not controllable. we need to use some trick to know this is your second login.
authentication
The Default Authenticaiton Scheme : Windows, it's the basic scheme for authenticate a windows user.
Second Cookies base Authentication scheme : SuperUserTwoStep. we need this to goto our custom login logic.
Authorize
the Authorize policies for specified scheme.
a login page for login to SuperUserTwoStep scheme.
//startup
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme)
.AddCookie("SuperUserTwoStep",op=>op.LoginPath = "/account/superuser2steplogin");
services.AddAuthorization(op =>
{
op.AddPolicy("SuperUser", b => b.AddAuthenticationSchemes("SuperUserTwoStep")
.RequireAuthenticatedUser()
.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
// login
public static IDictionary<string, string> States { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
[Route("/account/superuser2steplogin")]
public async Task<IActionResult> LoginTwoStepConfirm(string returnUrl, [FromServices]IAuthorizationService authorizationService,
[FromServices]IAuthorizationPolicyProvider policyProvider)
{
var winresult = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
if (winresult.Succeeded)
{
if (States.TryGetValue(winresult.Principal.Identity.Name, out _))
{
States.Remove(winresult.Principal.Identity.Name);
var principal = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity(winresult.Principal.Claims,"twostepcookie"));
await HttpContext.SignInAsync("SuperUserTwoStep", principal);
return Redirect(returnUrl);
}
else
{
States[winresult.Principal.Identity.Name] = "1";
return Challenge(IISDefaults.AuthenticationScheme);
}
}
else
{
return Challenge(IISDefaults.AuthenticationScheme);
}
}
[Authorize("SuperUser")]
public IActionResult YourSecurePage()
{
return Content("hello world");
}
the most difficult thing is to track that this is the second time to login, I try to use cookie , but it doen't work, so I crate a static IDitionary<string,string> to track ,maybe use distributed cache is better
I think in my opinion you should consider using: Policy-based authorization with Requirements, basically you have different authorization requirements that you want to treat them on and AND basis
REQ1 and REQ2 and REQ3
Here you have the link to the documentation: Requirements
But you need to understand that identity != permissions, the guys that introduce this concept of policies to Microsoft created a project named: PolicyServer and it is opensource: PolicyServer Git and they created a pattern there of how you should use your policies. Basically, you have external and internal users that are authenticated against your AD, all internal users should have permissions assigned to a role. And you only decorate your controller action with the permission rule you created for that policy
[Authorize("PerformSurgery")]
public async Task<IActionResult> PerformSurgery()
{
// omitted
}
To understand the code and how they evaluate a policy, I think you should see the video they have online on the website: Policy Server
Hope this helps
In my MVC 5 web app I have this (in AccountController.cs):
// Used for XSRF protection when adding external sign ins
private const string XsrfKey = "XsrfId";
and
public string SocialAccountProvider { get; set; }
public string RedirectUri { get; set; }
public string UserId { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, SocialAccountProvider);
}
How exactly is it being used for protection?
Should I set the value of XsrfKey to something more random?
Take a look at ManageController methods LinkLogin and LinkLoginCallback:
//
// POST: /Manage/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId());
}
//
// GET: /Manage/LinkLoginCallback
public async Task<ActionResult> LinkLoginCallback()
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
if (loginInfo == null)
{
return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}
var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}
These are the methods that handle linking of external accounts (i.e. Google, Facebook, etc.). The flow goes like this:
User clicks "Link Account" button, which calls a POST to LinkLogin method.
LinkLogin returns ChallengeResult object, with callback url set to LinkLoginCallback method.
ChallengeResult.ExecuteResult is called by MVC framework, calls IAuthenticationManager.Challenge, which causes a redirect to the specific external login provider (let's say: google).
User authenticates with google, then google redirects to callback url.
The callback is handled with LinkLoginCallback. Here, we want to prevent XSRF and verify that the call was initiated by a user, from a page served by our server (and not by some malicious site).
Normally, if it was a simple GET-POST sequence, you would add a hidden <input> field with an anti-forgery token and compare it with a corresponding cookie value (that's how Asp.Net Anti-Forgery Tokens work).
Here, the request comes from external auth provider (google in our example). So we need to give the anti-forgery token to google and google should include it in the callback request. That's exactly what state parameter in OAuth2 was designed for.
Back to our XsrfKey: everything you put in AuthenticationProperties.Dictionary will be serialized and included in the state parameter of OAuth2 request - and consequentially, OAuth2 callback. Now, GetExternalLoginInfoAsync(this IAuthenticationManager manager, string xsrfKey, string expectedValue) will look for the XsrfKey in the received state Dictionary and compare it to the expectedValue. It will return an ExternalLoginInfo only if the values are equal.
So, answering your original question: you can set XsrfKey to anything you want, as long as the same key is used when setting and reading it. It doesn't make much sense to set it to anything random - the state parameter is encrypted, so no one expect you will be able to read it anyway.
Just leave it as is:
As the name of the member states it is a key:
private const string XsrfKey = "XsrfId";
It is defined in this manner to avoid "magic numbers" and then is used a little down in the scaffold code:
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
The value of the dictionary item is then set to the UserId property in the above code by using the XsrfKey member as the key.
IOW the code is already setting the XSRF dictionary item to the value of the user ID in the snippet. If you change the XsrfKey members value to anything else you will cause problems down the line, since the expected key "XsrfId" will have no value set.
If by changing it to something more random you are implying to change the value and not they key of the dictionary, or in other words, not set it to the user id then please see the following for an explanation of the anti forgery token inner workings.
http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages
I'm working on a controller in my project. I'm trying to do the following:
string role = "something";
var newRole = new IdentityRole();
newRole = _DbContext.Roles.Where(r => r.Name == role).FirstOrDefault().;
user.Roles.Add(newRole);
Apparently, user.Roles.Add() only takes a IdentityUserRole as a parameter, but newRole is of type IdentityRole. How can I convert from one to the other? Is there an alternate way to accomplish what I want to do?
Explicitly casting doesn't work.
Consider Using A UserManager
Generally, you would use the AddToRoleAsync() method from UserManager object to handle assigning roles to your users as opposed to adding them directly on the user object itself.
// Example of instantiating the User Manager
private readonly UserManager<ApplicationUser> _userManager;
public AccountController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
// Example of adding a user to a given role
public void AddUserToRole(string userId, string role)
{
var user = _userManager.FindByIdAsync(userId);
if (user != null)
{
await _userManager.AddToRoleAsync(user, role);
}
}
The UserManager class is responsible for ensuring that any relationships or information about your users is properly created, updated, and persisted as necessary, so it's generally preferred to delegate these type of responsibilities to it.
Regarding Direct Conversions
With regards to a direct conversion, you could use an approach similar to DavidG's response and simply use the Id from the User and the Role to build an object and add it to the user.
The IdentityUserRole object is there to link a user to a role, you just need to create a new instance of it. You also need the ID of the role you are adding, for example:
var roleName = "something";
var role = _DbContext.Roles.Single(r => r.Name == roleName);
var userRole = new IdentityUserRole
{
UserId = user.Id,
RoleId = role.Id
};
user.Roles.Add(userRole);
However, you should really be using the UserManager class for this. It has an AddToRoleAsync method that handles this for you.
If you really don't want to use the async method though, there are non-async extensions available. https://msdn.microsoft.com/en-us/library/dn497483(v=vs.108).aspx
I want to use the IoC container in a method to check a logged in users company code when they submit a payment. I have two certificates in my settings class and an IF else statement to differentiate between each one.
public static string FDGCreditCardUserID
{
get
{
if (BillingController.currentcompanycode == 5)
return ConfigurationManager.AppSettings["5FDGCreditCardUserID"];
else
return ConfigurationManager.AppSettings["6FDGCreditCardUserID"];
}
}
public static string FDGCreditCardPassword
{
get
{
if (BillingController.currentcompanycode == 5)
return ConfigurationManager.AppSettings["5FDGCreditCardPassword"];
else
return ConfigurationManager.AppSettings["6FDGCreditCardPassword"];
}
}
Then in my IoC container
x.For<IFDGService>().Use<FDGService>().SetProperty(s =>
{
s.Url = Settings.FDGURL;
s.UserID = Settings.FDGCreditCardUserID;
s.Password = Settings.FDGCreditCardPassword;
s.Certificate = Settings.FDGCreditCardCertFilePath;
});
I have an FDGService that checks credentials but does not return to the IoC on payment submit to check the company code and apply the correct certificate.
SubmitPayment Method where the creditcard control contains the correct company code when i run it.
How do i get my application to select the correct certificate based on the updated company code. Seeing as users can have different company codes based on policies selected for payment. One company code at the moment can either be 5 or 6.
public ActionResult SubmitPayment([ConvertJSON]List<PayModel> payments)
{
List<TransactionModel> transactions = new List<TransactionModel>();
foreach (var pymt in payments)
{
var policyNumber = pymt.PolicyNumber.Trim();
TransactionModel trans = new TransactionModel() { Payment = pymt };
if (pymt.Selected)
{
var creditCardControl = UpdateCreditCardControl(policyNumber);
If you are using StructureMap it uses "Greedy Initialization", meaning when the constructor is called it will call the constructor with the most amount of arguments or parameters passed in.
private IFDGService service;
public MyController(IFDGService service)
{
this.service = service;
}
Then service will be available after IoC.Configure() is called.
Call IoC.Configure() whereever the application is started. google "where does Mvc start" or something like that.
to change the company code set it somewhere other than an instance variable in the controller, like a static class, I know static is bad, get it working and then make it better, since that would be complex to modify, and then get; set; when you need to.
I have to go to meeting, kinda rushed, hope that helps
I have a WebApi controller that initially authenticates as a specific WebApi user. Subsequent accesses to the web api will pass a user that operations should be performed as, without having to actually authenticate as that user.
I have some services/managers that perform functions as those proper users as part of an MVC project. I now want to use those services and managers with the WebApi project, but I don't want to have to pass the user around.
I'm hoping I can temporarily change the identity of the Web Api call after the user passed in the Web Api call has been validated, but I want to make sure that when the call is complete, the cookie returned is for the validation of the WebApi user, not the end user that is represented as a part of the call.
My question is, what can I do to temporarily change the identity to the validated user in the call, and then change back to the web api identity?
Loosely using code from the links in the post, I created a IDisposable object that would temporarily change the identity.
Usage is:
try
{
using(new Impersonate(userManager, userName))
{
/* do your stuff as userName */
}
}
catch (ImpersonateException) {}
The Impersonate class is as follows:
public class Impersonate : IDisposable
{
private UserManager<ApplicationUser> userManager;
public Impersonate(UserManager<ApplicationUser> userManager, string userName)
{
this.userManager = userManager;
if (ValidateUser(userName))
{
this.ImpersonateUser(userName);
}
else
{
throw new ImpersonateException("Current user does not have permissions to impersonate user");
}
}
private bool ValidateUser(string userName)
{
/* validate that the current user can impersonate userName */
}
public void Dispose()
{
this.RevertImpersonation();
}
private void ImpersonateUser(string userName)
{
var context = HttpContext.Current;
var originalUsername = context.User.Identity.Name;
var impersonatedUser = this.userManager.FindByName(userName);
var impersonatedIdentity = impersonatedUser.GenerateUserIdentity(this.userManager, "Impersonation");
impersonatedIdentity.AddClaim(new Claim("UserImpersonation", "true"));
impersonatedIdentity.AddClaim(new Claim("OriginalUsername", originalUsername));
var impersonatedPrincipal = new ClaimsPrincipal(impersonatedIdentity);
context.User = impersonatedPrincipal;
Thread.CurrentPrincipal = impersonatedPrincipal;
}
private void RevertImpersonation()
{
var context = HttpContext.Current;
if (!ClaimsPrincipal.Current.IsImpersonating())
{
throw new ImpersonationException("Unable to remove impersonation because there is no impersonation");
}
var originalUsername = ClaimsPrincipal.Current.GetOriginalUsername();
var originalUser = this.userManager.FindByName(originalUsername);
var originalIdentity = originalUser.GenerateUserIdentity(this.userManager);
var originalPrincipal = new ClaimsPrincipal(originalIdentity);
context.User = originalPrincipal;
Thread.CurrentPrincipal = originalPrincipal;
}
}
This differs the linked code in that it only sets the identity temporarily, so doing having to SignIn/SignOut is not required.
Also, since the bulk of the work is done in the constructor, I had to remove the Async aspects that the linked code uses. There might be a way around it, but I'm not experienced enough with Async, or patient enough to bother.