I want to move this service to logic for using on everywhere, but i can't successful because it was coming from the controller.
I have two services. There read caches and I use them in the controller layer when authenticating.
my first logic is reading companyId in cache
public virtual int GetCompanyIdFromCache(int id)
{
_memCache.TryGetValue(id, out int companyId);
return companyId;
}
My second service is also on the controller. (helps me find the user's id)
[HttpGet]
[Route("GetCompanyId")]
public int GetCompanyPublicId()
{
if (User.Identity is ClaimsIdentity claimsIdentity)
{
var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;
var companyId = _userService.GetCompanyIdFromCache(Convert.ToInt32(userId));
return companyId;
}
throw new ArgumentException("Can't be found Company");
}
I want to use this method everywhere, so i want to move the second service completely to logic layer but User field comes from ControllerBase (on HttpContext i guess) and I can't move it to logic
if (User.Identity is ClaimsIdentity claimsIdentity)
What should I do to refactor the logic layer?
As far as I know, the User.Identity is ClaimsIdentity which is a property in the controllerbase. We couldn't directly use it in the other methods.
If you want to access the User.Identity in other service method, I suggest you could try to inject the httpaccessor service and get the ClaimsIdentity from it.
More details, you could refer to below codes:
Create myclass:
public class Myclass
{
public IHttpContextAccessor _accessor { get; set; }
public Myclass(IHttpContextAccessor accessor)
{
_accessor = accessor;
var re = accessor.HttpContext.User.Identity as ClaimsIdentity;
int i = 0;
}
public string GetName() {
var re = _accessor.HttpContext.User.Identity as ClaimsIdentity;
string name = re.Claims.First(x => x.Type == "name").Value;
return name;
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
services.AddHttpContextAccessor();
services.AddScoped(typeof(Myclass));
}
Usage:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public Myclass test { get; set; }
public HomeController(ILogger<HomeController> logger, Myclass _test)
{
_logger = logger;
test = _test;
}
public async Task<IActionResult> IndexAsync()
{
var claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
claimsIdentity.AddClaim(new Claim("name", "aaaa"));
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity)
);
return View();
}
public async Task<IActionResult> PrivacyAsync()
{
var re= test.GetName();
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Result:
Related
I am using .net 6
I am trying to use CosmosDb and I was following this tutorial. The problem is that they are instantiating based only with the container id that is set in appsettings.json
This is how Program.cs
static async Task<CosmosDbService> InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection)
{
var databaseName = configurationSection["DatabaseName"];
var containerName = configurationSection["ContainerName"];
var account = configurationSection["Account"];
var key = configurationSection["Key"];
var client = new CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
var cosmosDbService = new CosmosDbService(client, databaseName, containerName);
return cosmosDbService;
}
builder.Services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(builder.Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
And this is how the controller looks like:
namespace demoCosmoDB.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UserController : Controller
{
private readonly ICosmosDbService _cosmosDbService;
public UserController(ICosmosDbService cosmosDbService)
{
_cosmosDbService = cosmosDbService ?? throw new ArgumentNullException(nameof(cosmosDbService));
}
[HttpGet]
[Route("{Id:int}")]
public async Task<IActionResult> GetUser(string Id)
{
return Ok(await _cosmosDbService.GetUser(Id));
}
}
}
DbService is just an interface on what to implement:
Suppose that I have another controller ArticleController, How can instantiate with the contaienr id e.g "Articles"?
I tried:
static async Task<CosmosClient> InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection)
{
var account = configurationSection["Account"];
var key = configurationSection["Key"];
var client = new CosmosClient(account, key);
return client;
}
But I do not know how to modify the rest:
builder.Services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(builder.Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
....
Please do not create a client instance per Container. Create a single CosmosClient instance and use it to access any and all Containers in the same account.
For example:
static CosmosClient InitializeCosmosClientInstance(IConfigurationSection configurationSection)
{
var account = configurationSection["Account"];
var key = configurationSection["Key"];
var client = new CosmosClient(account, key);
return client;
}
builder.Services.AddSingleton<CosmosClient>(InitializeCosmosClientInstance(builder.Configuration.GetSection("CosmosDb")));
namespace demoCosmoDB.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UserController : Controller
{
private readonly Container _cosmosContainer;
public UserController(CosmosClient cosmosClient)
{
if (cosmosClient == null) {
throw new ArgumentNullException(nameof(cosmosClient));
}
_cosmosContainer = cosmosClient.GetContainer("dbName", "containerName");
// you can have different containers for different controllers
}
[HttpGet]
[Route("{Id:int}")]
public async Task<IActionResult> GetUser(string Id)
{
// use the Container to perform your operations
}
}
}
My Application: .Net Core 3.1 Web application using Microservice architecture; Identity for Authorization & Authentication as separate Microservice API.
I have extended the standard AspNetUsers and AspNetRoles table with custom fields.
Getting the following error when I am trying to create a new Role using Identity RoleManager.
Cannot access a disposed context instance. A common cause of this
error is disposing a context instance that was resolved from
dependency injection and then later trying to use the same context
instance elsewhere in your application. This may occur if you are
calling 'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances. Object name: 'MembershipDBContext'.
Find my Code below
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var idenConnectionString = Configuration["DbContextSettings:IdentityConnectionString"];
var userConnectionString = Configuration["DbContextSettings:UserConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var builder = new NpgsqlConnectionStringBuilder(idenConnectionString)
{
Password = dbPassword
};
var userBuilder = new NpgsqlConnectionStringBuilder(userConnectionString)
{
Password = dbPassword
};
services.AddDbContext<MembershipDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddDbContext<UserDBContext>(opts => opts.UseNpgsql(userBuilder.ConnectionString));
services.AddIdentity<MembershipUser, MembershipRole>(options =>
{
options.Password.RequiredLength = 8;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+ ";
options.SignIn.RequireConfirmedEmail = false;
}).AddRoles<MembershipRole>().AddEntityFrameworkStores<MembershipDBContext>()
.AddDefaultTokenProviders();
services.AddTransient<IIdentityMSService, IdentityMSService>();//IdentityMS
services.AddTransient<IAdministrationService, AdministrationService>();//IdentityMS
services.AddTransient<IIdentityMSRepository, IdentityMSRepository>();//IdentityMS
services.AddTransient<IAdministrationRepository, AdministrationRepository>();//IdentityMS
services.AddTransient<UserDBContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddMediatR(typeof(Startup));
RegisterServices(services);
}
MembershipDBContext.cs
public class MembershipDBContext : IdentityDbContext<MembershipUser,MembershipRole,string>
{
public MembershipDBContext(DbContextOptions<MembershipDBContext> options) : base(options)
{
}
}
AdministrationController
public class AdministrationController : Controller
{
private readonly IAdministrationMVCService _adminService;
public AdministrationController(IAdministrationMVCService adminService)
{
_adminService = adminService;
}
// GET: AdministrationController/Create
public ActionResult Create()
{
return View();
}
// POST: AdministrationController/Create
[HttpPost]
//[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateAsync(MembershipRole rm)
{
if (ModelState.IsValid)
{
try
{
rm.CompanyId = 1;
await _adminService.AddRoles(rm);
//return RedirectToAction(nameof(Index));
return View();
}
catch
{
return View();
}
}
return View();
}
}
AdministrationApiController
[HttpPost]
public void Post([FromBody] MembershipRole role)
{
_adminMSService.AddRoles(role);
}
AdministrationRepository
public class AdministrationRepository : IAdministrationRepository
{
private readonly RoleManager<MembershipRole> _roleManager;
private readonly UserManager<MembershipUser> _userManager;
public AdministrationRepository(RoleManager<MembershipRole> roleManager, UserManager<MembershipUser> userManager)
{
_roleManager = roleManager;
_userManager = userManager;
}
public async Task AddRolesAsync(MembershipRole rvm)
{
try
{
IdentityResult result = await _roleManager.CreateAsync(rvm);
if (result.Succeeded) {
}
}
catch (Exception ex)
{
throw;
}
}
}
I tried making the services and repositories as singleton but it didn't work either. What am I missing here? Any insights?
Solved the issue! As DavidG pointed out in his comment, I missed the 'await' keyword at certain places like AdministrationApiController.
Posting as an 'Answer' here, as I am unable to mark the comment as 'Answer'.
I have the following code:
[Route("resources/avatar")]
[ApiController]
public class AvatarController : ControllerBase
{
private readonly ApplicationDbContext database;
private readonly IWebHostEnvironment environment;
private readonly IUserManagerWrapper userManagerWrapper;
public AvatarController(IUserManagerWrapper userManagerWrapper, IWebHostEnvironment environment,
ApplicationDbContext database)
{
this.userManagerWrapper = userManagerWrapper;
this.environment = environment;
this.database = database;
}
[HttpGet]
[Route("")]
public async Task<IActionResult> Index()
{
if (User == null) return DefaultImage();
var user = await this.userManagerWrapper.GetUserAsync(User);
if ((user?.Avatar?.Length ?? 0) == 0) return DefaultImage();
return File(user.Avatar, "image/jpeg");
}
}
I have an issue with testing this Index Page.
User is a property that comes from ControllerBase and is of type ClaimsPrincipal.
I used a wrapper where I would wrap the usermanager and then use an interface that I would mock.
The problem with this approach is I cannot set this ClaimsPrincipal to null because it is a read-only.
This was my test:
[TestFixture]
public class AvatarController_Should
{
[Test]
public async Task IndexReturnsDefaultImage()
{
var hostingEnvironmentMock = new Mock<IWebHostEnvironment>();
var dabataseName = nameof(IndexReturnsDefaultImage);
var options = AvatarTestUtil.GetOptions(dabataseName);
var userManagerWrapperMock = new Mock<IUserManagerWrapper>();
using (var actAndAssertContext = new ApplicationDbContext(options))
{
var sut = new AvatarController(userManagerWrapperMock.Object, hostingEnvironmentMock.Object, actAndAssertContext);
}
}
}
public class AvatarTestUtil
{
public static DbContextOptions<ApplicationDbContext> GetOptions(string databaseName)
{
var serviceCollection = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
return new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName)
.UseInternalServiceProvider(serviceCollection)
.Options;
}
}
}
I am open to using a completely new approach.
This was how I used to do test on the identity before, but I am stuck now.
Looking at the source code for ControllerBase, we can see User is defined as so
public ClaimsPrincipal User => HttpContext?.User;
So the User actually comes from HttpContext. But HttpContext is readonly as well. Digging into the source code deeper, though, we can see that HttpContext is derived from ControllerContext
public HttpContext HttpContext => ControllerContext.HttpContext;
Alas! ControllerContext actually has a setter in the concrete implementation!
public ControllerContext ControllerContext { get; set; }
We could set up a whole new ControllerContext here if we wanted. But we really just need ControllerContext.User. Luckily, that has a setter too. Since you only really need to set the User, we can do so directly here rather than newing up another ControllerContext.
using (var actAndAssertContext = new ApplicationDbContext(options))
{
var sut = new AvatarController(userManagerWrapperMock.Object, hostingEnvironmentMock.Object, actAndAssertContext);
sut.ControllerContext.HttpContext.User = null;
}
I want to create a custom Authorize attribute to be able to send a personalized response when it fails. There are many examples, but I could not find what I'm looking for.
When registering a policy, I add a "claim". Is it possible to access that registered claim within the custom attribute without having to pass the claim by parameter? or is it possible to know if the check of the claim happened and if not, return a personalized response? Thx!
public static void AddCustomAuthorization(this IServiceCollection serviceCollection)
{
serviceCollection.AddAuthorization(x =>
{
x.AddPolicy(UserPolicy.Read,
currentPolicy => currentPolicy.RequireClaim(UserClaims.Read));
});
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext authorizationFilterContext)
{
if (authorizationFilterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (!authorizationFilterContext.HttpContext.User.HasClaim(x => x.Value == "CLAIM_NAME")) // ACCESS TO REGISTER CLAIM => currentPolicy => currentPolicy.RequireClaim(UserClaims.Read)
{
authorizationFilterContext.Result = new ObjectResult(new ApiResponse(HttpStatusCode.Unauthorized));
}
}
}
}
[HttpGet]
[CustomAuthorizeAttribute(Policy = UserPolicy.Read)]
public async Task<IEnumerable<UserDTO>> Get()
{
return ...
}
You can use IAuthorizationPolicyProvider to get the policy and then use ClaimsAuthorizationRequirement.ClaimType to get a claim name. And since it has async API, it is better to use IAsyncAuthorizationFilter instead of IAuthorizationFilter. Try this:
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext authorizationFilterContext)
{
var policyProvider = authorizationFilterContext.HttpContext
.RequestServices.GetService<IAuthorizationPolicyProvider>();
var policy = await policyProvider.GetPolicyAsync(UserPolicy.Read);
var requirement = (ClaimsAuthorizationRequirement)policy.Requirements
.First(r => r.GetType() == typeof(ClaimsAuthorizationRequirement));
if (authorizationFilterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (!authorizationFilterContext.HttpContext
.User.HasClaim(x => x.Value == requirement.ClaimType))
{
authorizationFilterContext.Result =
new ObjectResult(new ApiResponse(HttpStatusCode.Unauthorized));
}
}
}
}
This attribute takes an array of strings, which was needed in my case. I needed to pass different users roles to this attribute and return result based on some custom logic.
public class CustomAuthFilter : AuthorizeAttribute, IAuthorizationFilter
{
public CustomAuthFilter(params string[] args)
{
Args = args;
}
public string[] Args { get; }
public void OnAuthorization(AuthorizationFilterContext context)
{
//Custom code ...
//Resolving a custom Services from the container
var service = context.HttpContext.RequestServices.GetRequiredService<ISample>();
string name = service.GetName(); // returns "anish"
//Return based on logic
context.Result = new UnauthorizedResult();
}
}
You can decorate your controller with this attribute as shown below
[CustomAuthFilter("Anish","jiya","sample")]
public async Task<IActionResult> Index()
Sample is a class that returns a hard coded string
public class Sample : ISample
{
public string GetName() => "anish";
}
services.AddScoped(); //Register ISample, Sample as scoped.
FOR ASYNCHRONOUS SUPPORT use IAsyncAuthorizationFilter
public class CustomAuthFilter : AuthorizeAttribute, IAsyncAuthorizationFilter
{
public CustomAuthFilter(params string[] args)
{
Args = args;
}
public string[] Args { get; }
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
//DO Whatever...
//Resolve Services from the container
var service = context.HttpContext.RequestServices.GetRequiredService<ISample>();
var httpClientFactory = context.HttpContext.RequestServices.GetRequiredService<IHttpClientFactory>();
string name = service.GetName();
using var httpClient = httpClientFactory.CreateClient();
var resp = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos/1");
var data = await resp.Content.ReadAsStringAsync();
//Return based on logic
context.Result = new UnauthorizedResult();
}
}
Hope that helps..
I am creating a Core 2.0 Web API project that uses JWT for authentication and authorization. My controller methods that I want to secure are all decorated with the Authorize attribute.
This is working. If I pass the JWT in the Bearer header, I get a 200. If I fail to pass the JWT, I get the 401. All working. In my JWT, I have stored the User ID in the 'UserId' field when authorizing..
var claimsdata = new[] {
new Claim("UserId", user.Id.ToString()),
I then have an extension method:
public static string GetUserId(this IPrincipal user)
{
if (user == null)
return string.Empty;
var identity = (ClaimsIdentity)user.Identity;
IEnumerable<Claim> claims = identity.Claims;
return claims.FirstOrDefault(s => s.Type == "UserId")?.Value;
}
On my controller method, with 'Authorize', I often need the ID of the user. So I call my GetUserId method. This works. However, I am unsure if this is the best way to get the Id from the token.
int.TryParse(User.GetUserId(), out _userId);
I need to use that code on all controllers. I can't do it in the constructor, as .. that's wrong I think.
Am I doing the right thing here?
ControllerBase contains User property that is type of ClaimsPrincipal
You can access user claims by User.Claims and no need for IPrincipal
Create a base controller which contains GetUserId method as protected
public abstract class BaseController : Controller
{
protected int GetUserId()
{
return int.Parse(this.User.Claims.First(i => i.Type == "UserId").Value);
}
}
And all controllers inherit form this, now all controllers can access UserId
Firstly I create IUserProvider interface with IHttpContextAccessor injection to make mocks for these interfaces in unit tests.
public interface IUserProvider
{
string GetUserId();
}
Than implementation is
public class UserProvider : IUserProvider
{
private readonly IHttpContextAccessor _context;
public UserProvider (IHttpContextAccessor context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public string GetUserId()
{
return _context.HttpContext.User.Claims
.First(i => i.Type == ClaimTypes.NameIdentifier).Value;
}
}
So you can use interface IUserProvider in your controller without inheritance
[Authorize]
[ApiController]
public class MyController : ControllerBase
{
private readonly IUserProvider _userProvider;
public MyController(IUserProvider userProvider)
{
_userProvider = userProvider ?? throw new ArgumentNullException(nameof(userProvider ));
}
[HttpGet]
[Route("api/My/Something")]
public async Task<ActionResult> GetSomething()
{
try
{
var userId= _userProvider.GetUserId();
}
}
}
Also you can use
Extension Method
like this
public static long GetUserID(this ClaimsPrincipal User)
{
return long.Parse(User.Claims.First(i => i.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value);
}
and implement in your controller like this
[HttpDelete("DeleteAddress")]
public async Task<IActionResult> DeleteAddress([FromQuery] long AddressID)
{
try
{
long userID = this.User.GetUserID();
await _addressService.Delete(userID, AddressID);
return Ok();
}
catch (Exception err)
{
return Conflict(err.Message);
}
}
I hope it will help you
var authenticatedUser = User.Identities.Select(c => c.Claims).ToArray()[0].ToArray()[0];
var userid = await userManager.FindByNameAsync(authenticatedUser['email']).Id;