How to get User ID inside a separate assembly - c#

I'm working on an application which has two projects:
Core - houses the data access layer using repository pattern and domain-driven design
UI - using ASP.Net MVC. Currently, I am able to get the current logged in user's info(id, name, etc..) inside the UI controller via the User property like this:
using Microsoft.AspNet.Identity;
public class ExamController : Controller
{
IExaminationRepository _repository;
public ExamController()
{
_repository = RepositoryFactory.Get<IExaminationRepository>();
}
[HttpPost]
[Authorize(Roles = "Examiner")]
public ActionResult Create(ExamViewModel viewModel)
{
try
{
ExaminationDomain domain = Mapper.Map<ExamViewModel, ExaminationDomain>(viewModel);
//TODO: Move this to the repository
domain.AuthorId = User.Identity.GetUserId();
_repository.Add(domain);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
I would like to move the line: domain.AuthorId = User.Identity.GetUserId(); to my repository concrete implementation like this:
using Microsoft.AspNet.Identity;
using System.Security.Principal;
internal class ExaminationRepository
{
public DBEntities context;
public IPrincipal User;
public ExaminationRepository(DBEntities context)
{
this.context = context;
//I'd like to instantiate User property here:
this.User = "not sure what to instantiate with";
}
public void Add(ExaminationDomain domain)
{
Examination newExam = Mapper.Map<ExaminationDomain, Examination>(domain);
newExam.AuthorId = User.Identity.GetUserId();
newExam.CreatedBy = User.Identity.Name;
newExam.CreatedDate = DateTime.Now;
context.Examinations.Add(newExam);
context.SaveChanges();
}
But I am not sure what to instantiate the User property to in the constructor. I've read some suggestions to use WindowsIdentity.GetCurrent().User instead of creating a user property but this doesn't contain the user id, only user name.
Any other suggestions on getting user info?
I'd really appreciate some help on this..
Thanks,

I would decouple your repository from the httpcontext with a custom manager. For example I have a interface called IAUthenticationManager
public interface IAUthenticationManager
{
string CurrentUserId();
bool HasCurrentUserRole(string roleName),
}
Easy to test and fully decoupled.

This won't work easily since the repository can be used in many different contexts, even such contexts where user is not set. If you create a concrete dependency in your constructor, your repository will no longer be an independent data provider.
For example, referencing
HttpContext.Current.User.Identity
directly would create a dependency to a web context and the repository would be unusable in non-web contexts.
The best you could do is just to let the repository client provide this:
public void Add(ExaminationDomain domain, IPrincipal principal)
{
Examination newExam = Mapper.Map<ExaminationDomain, Examination>(domain);
newExam.AuthorId = principal.Identity.GetUserId();
newExam.CreatedBy = principal.Identity.Name;
newExam.CreatedDate = DateTime.Now;
context.Examinations.Add(newExam);
context.SaveChanges();
}
or (which could be possible)
public ExaminationRepository(DBEntities context, IPrincipal user)
{
this.context = context;
this.user = user;
}
The latter case could still be correctly resolved by an IoC container if you tell the container how to resolve the dependency.
In a web context, you could set the container to resolve it to HttpContext.Current.User.
In a non-web context, you could set the container to resolve it to WindowsIdentity.GetCurrent().User.

use HttpContext class witch is a singleton class:
first add a using to Microsoft.AspNet.Identity;
and then you can do some thing like this:
HttpContext.Current.User.Identity.GetUserId();
since GetUserId is an extension method you have a reference to Microsoft.AspNet.Identity
but if you need to access user information in several places of you app I suggest to have a wrapper class with properties that you need and instantiate when user logs in then store it in session variable this way you have two benefits:
1- you don't need to query db to get username, email etc.. on each user info usage across the app.
2- you don't need assembly that your repository lives to aspnet identity.

You need to instantiate this.User with identity information of current thread:
this.User = System.Threading.Thread.CurrentPrincipal;
var currentIdentity = (System.Security.Claims.ClaimsIdentity)User.Identity;
var userId = currentIdentity.Claims
.Where(p => p.Type.EndsWith("nameidentifier")).Single().Value;
Note that the type of CurrentPrincipal.Identity is an IIdentity. You can cast it to System.Security.Claims.ClaimsIdentity, which contains a property named Claims. This property contains all your claims, including userid and 3rd party token (e.g. Facebook token).
To retrieve UserId, find a claims with Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"

Related

Better approach to check users access in Web Api, Service layer, DB

Users are assigned to one or more departments.
Users have one or more roles for instance, Read Own role can only view his/her Tasks. While Team Member role can view and edit others Tasks within department he/she is assigned to.
User with role Admin can view and edit all Tasks in the system.
Due to unauthorized access prevention and performance reasons we want to pass the current logged in user id all the way down to the database to be able only fetch the records he/she has access to.
Our system design is:
Web API -> Business/Service layer -> Repositories -> DB
Currently we are passing User id from web API to service layer in each method where it checks for instance if user has role Team Member (who can view/edit other users Tasks within departments he has access to)
and gets all the Departments he has access to and then that is passed further to the Repositories.
Is there any better approach to avoid passing user id in each method?
What is the best place in the above design to check for users Access?
We ideally want the method without user id parameter to be able to use the same classes for reporting in another application.
Any ideas?
Use dependency injection to inject some ICurrentUser instance to the services that require the user id to perform queries and other tasks.
public interface ICurrentUser
{
int UserId { get; }
}
public class AspNetCurrentUser : ICurrentUser
{
public int UserId { get { return HttpContext.Current.User.GetUserId<int>(); } }
}
public class Service : IService
{
private readonly ICurrentUser _currentUser;
public Service(ICurrentUser currentUser)
{
_currentUser = currentUser;
}
public object WorkWithUserId()
{
return _currentUser.UserId;
}
}
Have a Security Layer (comprised of classes that decorate your service layer classes) that checks if the user has rights to raise the request.
For instance if your Web API call is to ../viewTask/456 check if the user is Admin, Team member of the department to which the task belongs to or if its his/her own task.
The decorator classes pass down to the wrapped service layer class if the access control check passes or raise an Unauthorized exception if it fails.
Something like...
public class SecuredTaskController : ApiController
{
private IContext _context;
private ITaskService _taskService;
// other services needed for access check (eg. userService?)
public SecuredTaskController(ITaskService taskService, IContext context
// other services needed for access check (eg. userService?)
)
{
_taskService = taskService;
_context = context;
}
public IHttpActionResult Get(Task task)
{
if (hasGetAccess(task, _context.UserId))
return Ok(_taskService.Get(task));
else
return Unauthorized();
}
private bool hasGetAccess(Task task, long userId)
{
// check if userId has acces to get task
}
}

How to get current user in asp.net core

I want to get the current user, so I can access fields like their email address.
But I can't do that in asp.net core.
This is my code:
HttpContext almost is null in constructor of controller.
It's not good to get a user in each action. I want to get the user's information once and save it to ViewData;
public DashboardController()
{
var user = HttpContext.User.GetUserId();
}
User.FindFirst(ClaimTypes.NameIdentifier).Value
EDIT for constructor
Below code works:
public Controller(IHttpContextAccessor httpContextAccessor)
{
var userId = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value
}
Edit for RTM
You should register IHttpContextAccessor:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
Simple way that works and I checked.
private readonly UserManager<IdentityUser> _userManager;
public CompetitionsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
var user = await _userManager.GetUserAsync(HttpContext.User);
then you can all the properties of this variables like user.Email. I hope this would help someone.
Edit:
It's an apparently simple thing but bit complicated cause of different types of authentication systems in ASP.NET Core. I update cause some people are getting null.
For JWT Authentication (Tested on ASP.NET Core v3.0.0-preview7):
var email = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
var user = await _userManager.FindByEmailAsync(email);
I have to say I was quite surprised that HttpContext is null inside the constructor. I'm sure it's for performance reasons. Have confirmed that using IPrincipal as described below does get it injected into the constructor. Its essentially doing the same as the accepted answer, but in a more interfacey-way.
For anyone finding this question looking for an answer to the generic "How to get current user?" you can just access User directly from Controller.User. But you can only do this inside action methods (I assume because controllers don't only run with HttpContexts and for performance reasons).
However - if you need it in the constructor (as OP did) or need to create other injectable objects that need the current user then the below is a better approach:
Inject IPrincipal to get user
First meet IPrincipal and IIdentity
public interface IPrincipal
{
IIdentity Identity { get; }
bool IsInRole(string role);
}
public interface IIdentity
{
string AuthenticationType { get; }
bool IsAuthenticated { get; }
string Name { get; }
}
IPrincipal and IIdentity represents the user and username. Wikipedia will comfort you if 'Principal' sounds odd.
Important to realize that whether you get it from IHttpContextAccessor.HttpContext.User, ControllerBase.User or ControllerBase.HttpContext.User you're getting an object that is guaranteed to be a ClaimsPrincipal object which implements IPrincipal.
There's no other type of User that ASP.NET uses for User right now, (but that's not to say other something else couldn't implement IPrincipal).
So if you have something which has a dependency of 'the current user name' that you want injected you should be injecting IPrincipal and definitely not IHttpContextAccessor.
Important: Don't waste time injecting IPrincipal directly to your controller, or action method - it's pointless since User is available to you there already.
In startup.cs:
// Inject IPrincipal
services.AddHttpContextAccessor();
services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
Then in your DI object that needs the user you just inject IPrincipal to get the current user.
The most important thing here is if you're doing unit tests you don't need to send in an HttpContext, but only need to mock something that represents IPrincipal which can just be ClaimsPrincipal.
One extra important thing that I'm not 100% sure about. If you need to access the actual claims from ClaimsPrincipal you need to cast IPrincipal to ClaimsPrincipal. This is fine since we know 100% that at runtime it's of that type (since that's what HttpContext.User is). I actually like to just do this in the constructor since I already know for sure any IPrincipal will be a ClaimsPrincipal.
If you're doing mocking, just create a ClaimsPrincipal directly and pass it to whatever takes IPrincipal.
Exactly why there is no interface for IClaimsPrincipal I'm not sure. I assume MS decided that ClaimsPrincipal was just a specialized 'collection' that didn't warrant an interface.
Have another way of getting current user in Asp.NET Core - and I think I saw it somewhere here, on SO ^^
// Stores UserManager
private readonly UserManager<ApplicationUser> _manager;
// Inject UserManager using dependency injection.
// Works only if you choose "Individual user accounts" during project creation.
public DemoController(UserManager<ApplicationUser> manager)
{
_manager = manager;
}
// You can also just take part after return and use it in async methods.
private async Task<ApplicationUser> GetCurrentUser()
{
return await _manager.GetUserAsync(HttpContext.User);
}
// Generic demo method.
public async Task DemoMethod()
{
var user = await GetCurrentUser();
string userEmail = user.Email; // Here you gets user email
string userId = user.Id;
}
That code goes to controller named DemoController. Won't work without both await (won't compile) ;)
It would appear that as of now (April of 2017) that the following works:
public string LoggedInUser => User.Identity.Name;
At least while within a Controller
Perhaps I didn't see the answer, but this is how I do it.
.Net Core --> Properties --> launchSettings.json
You need to have change these values
"windowsAuthentication": true, // needs to be true
"anonymousAuthentication": false, // needs to be false
Startup.cs --> ConfigureServices(...)
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
MVC or Web Api Controller
private readonly IHttpContextAccessor _httpContextAccessor;
//constructor then
_httpContextAccessor = httpContextAccessor;
Controller method:
string userName = _httpContextAccessor.HttpContext.User.Identity.Name;
Result is userName e.g. = Domain\username
I know there area lot of correct answers here, with respect to all of them I introduce this hack :
In StartUp.cs
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
and then everywhere you need HttpContext you can use :
var httpContext = new HttpContextAccessor().HttpContext;
Hope it helps ;)
My problem was to access the logged in User as an object in the cshtml file. Considering you wanted the user in ViewData, this approach might be helpful:
In the cshtml file
#using Microsoft.AspNetCore.Identity
#inject UserManager<ApplicationUser> UserManager
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
#UserManager.FindByNameAsync(UserManager.GetUserName(User)).Result.Email
</title>
</head>
<body>
</body>
</html>
In addition to existing answers I'd like to add that you can also have a class instance available app-wide which holds user-related data like UserID etc.
It may be useful for refactoring e.g. you don't want to fetch UserID in every controller action and declare an extra UserID parameter in every method related to Service Layer.
I've done a research and here's my post.
You just extend your class which you derive from DbContext by adding UserId property (or implement a custom Session class which has this property).
At filter level you can fetch your class instance and set UserId value.
After that wherever you inject your instance - it will have the necessary data (lifetime must be per request, so you register it using AddScoped method).
Working example:
public class AppInitializationFilter : IAsyncActionFilter
{
private DBContextWithUserAuditing _dbContext;
public AppInitializationFilter(
DBContextWithUserAuditing dbContext
)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next
)
{
string userId = null;
int? tenantId = null;
var claimsIdentity = (ClaimsIdentity)context.HttpContext.User.Identity;
var userIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdClaim != null)
{
userId = userIdClaim.Value;
}
var tenantIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == CustomClaims.TenantId);
if (tenantIdClaim != null)
{
tenantId = !string.IsNullOrEmpty(tenantIdClaim.Value) ? int.Parse(tenantIdClaim.Value) : (int?)null;
}
_dbContext.UserId = userId;
_dbContext.TenantId = tenantId;
var resultContext = await next();
}
}
For more information see my answer.
This is old question but my case shows that my case wasn't discussed here.
I like the most the answer of Simon_Weaver (https://stackoverflow.com/a/54411397/2903893). He explains in details how to get user name using IPrincipal and IIdentity. This answer is absolutely correct and I recommend to use this approach. However, during debugging I encountered with the problem when ASP.NET can NOT populate service principle properly. (or in other words, IPrincipal.Identity.Name is null)
It's obvious that to get user name MVC framework should take it from somewhere. In the .NET world, ASP.NET or ASP.NET Core is using Open ID Connect middleware.
In the simple scenario web apps authenticate a user in a web browser. In this scenario, the web application directs the user’s browser to sign them in to Azure AD. Azure AD returns a sign-in response through the user’s browser, which contains claims about the user in a security token.
To make it work in the code for your application, you'll need to provide the authority to which you web app delegates sign-in.
When you deploy your web app to Azure Service the common scenario to meet this requirements is to configure web app: "App Services" -> YourApp -> "Authentication / Authorization" blade -> "App Service Authenticatio" = "On" and so on (https://github.com/Huachao/azure-content/blob/master/articles/app-service-api/app-service-api-authentication.md). I beliebe (this is my educated guess) that under the hood of this process the wizard adjusts "parent" web config of this web app by adding the same settings that I show in following paragraphs.
Basically, the issue why this approach does NOT work in ASP.NET Core is because "parent" machine config is ignored by webconfig. (this is not 100% sure, I just give the best explanation that I have). So, to meke it work you need to setup this manually in your app.
Here is an article that explains how to manyally setup your app to use Azure AD.
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/aspnetcore2-2
Step 1: Register the sample with your Azure AD tenant.
(it's obvious, don't want to spend my time of explanations).
Step 2: In the appsettings.json file:
replace the ClientID value with the Application ID from the application you registered in Application Registration portal on Step 1.
replace the TenantId value with common
Step 3: Open the Startup.cs file and in the ConfigureServices method, after the line containing .AddAzureAD insert the following code, which enables your application to sign in users with the Azure AD v2.0 endpoint, that is both Work and School and Microsoft Personal accounts.
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
Summary: I've showed one more possible issue that could leed to an error that topic starter is explained. The reason of this issue is missing configurations for Azure AD (Open ID middleware). In order to solve this issue I propose manually setup "Authentication / Authorization". The short overview of how to setup this is added.
Taking IdentityUser would also work. This is a current user object and all values of user can be retrieved.
private readonly UserManager<IdentityUser> _userManager;
public yourController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
var user = await _userManager.GetUserAsync(HttpContext.User);
If you are using the scafolded Identity and using Asp.net Core 2.2+ you can access the current user from a view like this:
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
#if (SignInManager.IsSignedIn(User))
{
<p>Hello #User.Identity.Name!</p>
}
else
{
<p>You're not signed in!</p>
}
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-2.2&tabs=visual-studio
Most of the answers show how to best handle HttpContext from the documentation, which is also what I went with.
I did want to mention that you'll want to check you project settings when debugging, the default is Enable Anonymous Authentication = true.
if (access token in header or query parameter)
{
// Set the claims like in the Account/Login action from the interactive login form
var claims = ...;
// Local helper method, is used in other places, too
var claimsIdentity = await SignInAsync(httpContext, claims, false);
// Set user for the current request
// This works in that it's in User.Identity, but the auth events won't fire
httpContext.User = new ClaimsPrincipal(claimsIdentity);
}
And
var userEmail = HttpContext.User.FindFirst(ClaimTypes.Email).Value;
After exploring many solutions, here is what worked for me with ASP.NET core 5.
var claims = new List<Claim>(){
new Claim("Id", _user.Id)
};
As shown in the above snippet, add custom "Id" type and set it to user id while preparing list of claims to be included in the Jwt Token generation.
Then simply use that claim to access the user(This method uniquely identifies the user by its Id).
var userEmail = User.FindFirstValue("Id");
var user = await _userManager.FindByIdAsync(userEmail);
Here is complete solution:
->Token generation helper method
public async Task<string> CreateToken()
{
var signingCredentials = GetSigningCredentials();
var claims = await GetClaims();
var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
return new JwtSecurityTokenHandler().WriteToken(tokenOptions);
}
private SigningCredentials GetSigningCredentials()
{
var key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("JWT_SECRET"));
var secret = new SymmetricSecurityKey(key);
return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);
}
private async Task<List<Claim>> GetClaims()
{
var claims = new List<Claim>(){
new Claim("Id", _user.Id)
};
return claims;
}
private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, List<Claim> claims)
{
var jwtSettings = _configuration.GetSection("JwtSettings");
var tokenOptions = new JwtSecurityToken(
issuer: jwtSettings.GetSection("ValidIssuer").Value,
audience: jwtSettings.GetSection("ValidAudience").Value,
expires: DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings.GetSection("ExpiresIn").Value)),
signingCredentials: signingCredentials,
claims: claims
);
return tokenOptions;
}
Here is code for Getting LoggedIn User:
[HttpGet("user")]
public async Task<ActionResult<User>> GetUser()
{
var userId = User.FindFirstValue("Id");
var user = await _userManager.FindByIdAsync(userId);
return Ok(new { User = User });
}
I use answer provided by #Ahmed for Identity
For getting the current user id, I use the following
var currentuserid = userManager.GetUserId(User);
For getting other fields related to logged user in AspNetUsers table, I use the following
var userorg = context.Users.Where(l=>l.Id== currentuserid).FirstOrDefaultAsync().Result.OrganizationId;
Hi if you want you can get id on claim like here
var userId = User.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Sub).Value;
I got my solution
var claim = HttpContext.User.CurrentUserID();
public static class XYZ
{
public static int CurrentUserID(this ClaimsPrincipal claim)
{
var userID = claimsPrincipal.Claims.ToList().Find(r => r.Type ==
"UserID").Value;
return Convert.ToInt32(userID);
}
public static string CurrentUserRole(this ClaimsPrincipal claim)
{
var role = claimsPrincipal.Claims.ToList().Find(r => r.Type ==
"Role").Value;
return role;
}
}

Injected IPrincipal is anonymous but Web API Controller User object is authenticated

I'm writing a Web API 2/MVC5 project and I wanted to unit test some code that had to work with an IPrincipal using ASP.Net Identity. Instead of relying on IPrincipal I wanted to abstract that behind my own IUserService. When I look at my injected IUserService the UserId and UserName are null.
public interface IUserService
{
string UserId { get; }
string UserName { get; }
}
The concrete implementation being used is:
public class UserService : IUserService
{
private IPrincipal _principal;
public UserService(IPrincipal principal)
{
_principal = principal;
}
public string UserName
{
get { return _principal.Identity.GetUserName(); }
}
public string UserId
{
get { return _principal.Identity.GetUserId(); }
}
}
This is using Ninject for dependency injection. Inside NinjectWebCommon.cs I have:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IBooksService>().To<BooksService>().InRequestScope();
kernel.Bind<DbContext>().To<ApplicationDbContext>().InRequestScope();
kernel.Bind<ApplicationDbContext>().To<ApplicationDbContext>().InRequestScope();
kernel.Bind<IUserStore<ApplicationUser>>().To<UserStore<ApplicationUser>>().InRequestScope();
kernel.Bind<UserManager<ApplicationUser>>().To<UserManager<ApplicationUser>>().InRequestScope();
kernel.Bind<IBookRepository>().To<BookRepository>().InRequestScope();
kernel.Bind<IUserService>().To<UserService>().InRequestScope();
kernel.Bind<IPrincipal>().ToMethod(ctx => HttpContext.Current.User);
}
If I create a Func<IPrincipal> and pass ()=>HttpContext.Current.User everything works fine. However, I don't see anyone needing to do this and all of the examples suggest this implementation.
Do you ever authenticate the user? Once the user is authenticated, you need to take care of creating an IIdentity and an IPrincipal. Then, you need to set Thread.CurrentPrincipal with the IPrincipal, and you also need to put the IPrincipal in the current HttpContext.
In order for a GenericIdentity to not be considered anonymous, the Name property must be a non-empty string. In order for a ClaimsIdentity to not be considered anonymous, the AuthenticationType property must be a non-null, non-empty string.
So, generally, you'll do the following:
// Perform authentication, usually using some kind of AuthenticationFilter or
// AuthorizationFilter.
// After authenticating, and still within the Auth*Filter,
// I'm going to use a GenericIdentity, but this can be converted to a
// ClaimsIdentity if you're using the default Name claim.
IIdentity identity = new GenericIdentity("myUserName", "myAuthenticationType");
// Again, you could use a ClaimsPrincipal, the concept, however, is the same.
IPrincipal principal = new GenericPrincipal(identity, null /* no roles */);
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
I did see that you mentioned the new ASP.Net Identity model is being used. So you'd definitely use ClaimsIdentity and ClaimsPrincipal in your code.
This is only my guess, but this may be faulty:
kernel.Bind<IUserService>().To<UserService>().InRequestScope();
First use of IUserService can occur before HttpContext.Current.User is set, so instead of current user, you get null, because at the moment UserService was created, HttpContext.Current.User was null. Since you have InRequestScope() defined, first created object of UserService is provided in subsequent uses, so principal is null all the time.
What I would probably do is just use HttpContext directly:
public class HttpContextBasedUserService : IUserService
{
public string UserName
{
get { return HttpContext.Current.User.Identity.GetUserName(); }
}
public string UserId
{
get { return HttpContext.Current.User.Identity.GetUserId(); }
}
}
If you want to use IUserService in desktop application, create implementation dedicated for desktop use.

Pattern for doing authorization in repository layer of MVC application

I have a Windows authenticated MVC application with a repository layer. All interaction by the controller with the database is done through the repository. Each controller has a reference to the repository:
public class PostController : Controller
{
private Repository db = new Repository();
[HttpPost]
public ActionResult DeletePost(int id)
{
// Authorize that the user is allowed to delete this post...
db.DeletePost(id);
}
}
My question is whether there is a good way to move my authorization logic into the repository layer. I'd like the Repository.DeletePost() function to refuse to delete posts that were not created by the authenticated user. The problem is that my repository does not know who the authenticated user is. The controller knows (via Controller.User).
Passing the Controller.User into the Repository constructor doesn't work, because the Controller.User is apparently not defined at the time when the constructor is called.
How can I inform the Repository of who the authenticated user is? Would it be best to just construct the Repository within each action? Or is it a bad idea to handle it in the repository layer?
Or is it a bad idea to handle it in the repository layer?
I think the Controller is a better place for your authorization. Let the repository be a gateway to the data and the controller be a gatekeeper to your application. I'd expect to see authorization/authentication logic as early in the life-cycle as possible.
Just do something like:
db.DeletePostForUser(id, User.Identity.UserId);
Then in your repository:
public void DeletePostForUser(int id, int userId)
{
var post = context.Posts.SingleOrDefault(m => m.PostId == id && m.User.UserId == userId)
if (post != null)
{
context.Posts.Remove(post);
context.SaveChanges();
}
}
Good suggestions from both #BigDaddy and #ChrisPratt.
I ended up solving this by creating a base controller, similar to this answer. My base controller class looks like:
public class BaseController : Controller
{
private ILog _log;
private Repository _db;
protected Repository Db
{
get
{
return _db ?? (_db = new Repository(User));
}
}
protected ILog Log
{
get
{
return _log ?? (_log = LogManager.GetLogger(this.GetType()));
}
}
}
All of my controllers inherit from this class, and have built-in access to a lazy-loaded Repository that has a reference to the currently authenticated user.

Entity tracking in multiple contexts

in a typical MVC3 app with an EF model, each controller instantiates its own copy of the model container. this means that if I were to create a class in a different file and it needed access to the model, it would need to instantiate its own container.
Consider the following:
namespace X.Web.Controllers
{
public class TestController : Controller
{
EFContainer db = new EFContainer();
public ActionResult Whatever()
{
User u = db.Users.Find(3);
...
}
if I wanted to abstract my fetching of a user in a class Auth then it would have to instantiate its own db since it doesn't have access to the controller's -- all fine until the controller wants to make changes to the returned object:
public ActionResult Whatever()
{
User u = Auth.GetUser();
u.Name = "ekkis";
db.SaveChanges();
...
}
since the user at that point belongs to a different context... so either I have to share my db with Auth or perhaps I'd have to do a moronic double-lookup:
public ActionResult Whatever()
{
int id = Auth.GetUserId();
User u = db.Users.Find(id);
u.Name = "ekkis";
db.SaveChanges();
...
}
what is the recommended way of dealing with this?
Why don't you pass in your model / EF context via constructor injection into your Auth class? That seems to be the most reasonable way (same applies to your controller actually once you have an IOC container set up).
public class Auth
{
public Auth(EFContainer db)
{
//...
}
}
Ideally you would also make this work based on an abstract interface so you can test Auth independently of EF.

Categories