How can I pass the authenticated User Id to the class library project in asp.net core via DI? - asp.net-core

I have a NLayers application:
asp.net core mvc
asp.net web api
and some of my class libraries:
DataLayer
DomainClasses
Models
Services
here is my BaseService in ServicesLayer:
public abstract partial class BaseService
{
protected BaseService(AppDbContext dbContext
, UserManager<MyApplicationUser> userManager
, int authenticatedUserId)
{
DbContext = dbContext;
AuthenticatedUserId = authenticatedUserId;
MyUserManager = userManager;
Init();
}
public AppDbContext DbContext { get; }
protected UserManager<MyApplicationUser> MyUserManager;
public string AuthenticatedUserId { get; }
protected virtual void Init()
{
//
}
...
}
and one of my child service classes:
public class BookService :BaseService
{
public BookService(AppDbContext dbContext
, UserManager<MyApplicationUser> userManager
, int authenticatedUserId)
:base(dbContext,userManager, authenticatedUserId)
{
}
}
I want to access the authenticated user id (from Asp net core) in my services (class library). How can I pass it via DI or something else?
Updated based on #Frank's suggestion:
public class CommonServicesContainer
{
public AppDbContext DbContext { get; set; }
public AppUserManager UserManager { get; set; }
public int AuthenticatedUserId{ get; set; }
public CommonServicesContainer(AppDbContext appDbContext, AppUserManager userManager, string authenticatedUserId)
{
DbContext = dbContext;
UserManager = userManager;
AuthenticatedUserId = autheticatedUserId;
}
}
my startup:
services.AddScoped<AppDbContext>();
services.AddScoped<AppUserManager>();
services.AddScoped(x =>
{
var authenticatedUserId = x.GetRequiredService<IHttpContextAccessor>().HttpContext.User.Identity.Name;
return new CommonServicesContainer(x.GetRequiredService<AppDbContext>()
, x.GetRequiredService<AppUserManager>()
, authenticatedUserId);
});
AccountController :
private readonly CommonServicesContainer _commonServicesContainer;
public AccountController(CommonServicesContainer commonServicesContainer)
{
_commonServicesContainer = commonServicesContainer;
// ...
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
// ...
if(ModelState.IsValid)
{
var isValid = await _usersService.AreCredentialsValidAsync(model.Username, model.Password);
if(isValid)
{
var foundUser = await _usersService.GetByUserNameAsync(model.Username);
await HttpContext.SignInAsync(
foundUser.SubjectId,
foundUser.UserName);
//_commonServicesContainer.AuthenticatedUserId = foundUser.Id;
// ...
}
// ...
}

You can do that by register a AuthenticatedUser type as a AddScoped.
class AuthenticatedUser {
public int? UserId {get;set;}
public bool IsAuthenticated => int.HasValue;
}
in Startup.cs of your AspNetCore project:
public IServiceProvider ConfigureServices(IServiceCollection services) {
...
services.AddScoped<AuthenticatedUser>();
...
}
Somewhere you do the authentication, you get the AuthenticatedUser and set the UserId.
Since AuthenticatedUser is added as scoped it acts as global (same instance) for the particular httprequest scope. So all .GetService<AuthenticatedUser> / .GetRequiredService<AuthenticatedUser> will have the same instance - within the same scope.
Each http-request has it is own scope, and thereby also their own AuthenticatedUser.
When the user is Authenticated, using AspNetCore Identity, you can find the AspNetUsers Id by:
if( httpContext.User.Identity.IsAuthenticated ) {
var userIdClaim = httpContext.User.Claims.SingleOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
var aspNetUsersId = userIdClaim?.Value ?? 0;
}
This can be done as Middleware, then setting AuthenticatedUser.UserId.

Related

How to convert ODataQueryOptions<DtoType> to ODataQueryOptions<EntityType> to query the underlying storage?

I have a webapi controller using ProductDTO type for clients but the repository is using a Product type.
I would like to use odata on my endpoint. I receive the ODataQueryOptions parameter and I want to pass it to repository (implemented using CosmosDB).
I cant seem to figure out how to convert from ODataQueryOptions<ProductDTO> to ODataQueryOptions<Product>.
[Route("api/[controller]")]
public class ProductsController<ProductsDTO, Product> : ControllerBase
{
IRepository<Product> _repository;
IMapper _mapper;
[HttpGet]
public async Task<ActionResult<IList<ProductDTO>>> Get(ODataQueryOptions<ProductDTO> queryOptions)
{
var mappedQueryOptions = ... // convert 'queryOptions' to ODataQueryOptions<Product> ???
var products = await _repository.Get(mappedQueryOptions);
return Ok(_mapper.Map<IEnumerable<Product>, IEnumerable<ProductDTO>>(products));
}
}
In my aspnetcore service composition I create and inject automapper
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new ProductProfile());
cfg.AddExpressionMapping();
});
internal class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<Product, ProductDto>().ReverseMap();
}
}
I managed to extract the queryoptions filter as a lambda expression Expression<Func<Product, bool>> (using automapper MapExpression) and passed it to repository , that works to a certain extent but I want to get the select , top, skip, etc. as well.
Any suggestions on how that could be done?
can you review the example?
[ODataRouteComponent("api")]
public class UsersController : ODataController
{
#region ctor
private readonly ILogger<UsersController> _logger;
private readonly IMapper _mapper;
private readonly UserManager<AspNetUser> _userManager;
public UsersController(IMapper mapper,
UserManager<AspNetUser> userManager,
ILogger<UsersController> logger)
{
_mapper = mapper;
_userManager = userManager;
_logger = logger;
}
#endregion
[HttpGet]
[EnableQuery(PageSize = 10)]
public async Task<IActionResult> Get(ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting all users", user.UserName);
return Ok(await _userManager.Users.GetAsync(_mapper, options));
}
[HttpGet]
[EnableQuery]
public async Task<IActionResult> Get(int key, ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting user with id {Key}", user.UserName, key.ToString());
return Ok(_userManager.Users.Get(_mapper, options).FirstOrDefault(x => x.Id == key));
}
}
services.AddControllers(opt =>{}).AddOData((opt, _) =>
{
opt.EnableQueryFeatures().AddRouteComponents("api", EdmModelBuilder.GetEdmModelv1());
});
public class MappingProfile : Profile
{
public MappingProfile() : base(nameof(WebUI))
{
CreateMap<AspNetUser, UserDto>();
}
}
internal static class EdmModelBuilder
{
internal static IEdmModel GetEdmModelv1()
{
ODataConventionModelBuilder builder = new();
builder.EnableLowerCamelCase();
#region UserDto
builder.EntitySet<UserDto>(nameof(DataListContext.Users))
.EntityType
.Page(25, 15);
var f5 = builder.Function("Get");
f5.Parameter<int>("key").Required();
f5.ReturnsFromEntitySet<UserDto>(nameof(DataListContext.Users));
#endregion
return builder.GetEdmModel();
}
}

Blazor : How to read appsetting.json from a class in .NET 6?

The following is working for me, but not sure this is the right way to do use DI in .NET6 blazor.
I have the following class
public class Authentication
{
private IConfiguration _configuration;
private AppState _appState;
public Authentication(IConfiguration Configuration, AppState appState)
{
_configuration = Configuration;
_appState = appState; ;
}
public async Task<AccessToken?> getAccessToken()
{
var tokenServer = _configuration.GetValue<string>("tokenUrl");
var clientID = _configuration.GetValue<string>("ABC:ClientID");
var clientSecret = _configuration.GetValue<string>("ABC:ClientSecret");
var grantType = _configuration.GetValue<string>("ABC:GrantType");
AccessToken? accessToken = null;
.............
............
return accessToken;
}
}
in my code behind of razor page
namespace XXXXXXXXXXX.Pages
{
public partial class Index
{
[Inject]
public ILogger<Index> _Logger { get; set; }
[Inject]
public IConfiguration Configuration { get; set; }
[Inject]
public AppState _appState { get; set; }
**Authentication auth;**
protected override void OnInitialized()
{
**auth = new Authentication(Configuration, _appState);**
base.OnInitialized();
}
private async Task HandleValidSubmit()
{
_Logger.LogInformation("HandleValidSubmit called");
auth.getAccessToken();
// Process the valid form
}
}
}
My Question is I was Expecting the DI to do its magic and Insert the Dependency in my class.
but to get this working i had to write
auth = new Authentication(Configuration, _appState);
I was expecting to instantiate
using auth = new Authentication() , but this one throws compiler error.

Admin lock or unlock account user in .Net Core

I am doing the management of a user's account when necessary I can Lock a user's account in case they violate it. Or can be unlocked if required. I got an error like this. Where am I wrong, I use .Net Core 5 to build my program. Error: "An unhandled exception occurred while processing the request.
NullReferenceException: Object reference not set to an instance of an object."
enter image description here
Interface
public bool LockUser(string email);
public bool UnlockUser(string email);
Repo
public bool LockUser(string email)
{
var userTask = _userManager.FindByEmailAsync(email);
userTask.Wait();
var user = userTask.Result;
var lockUserTask = _userManager.SetLockoutEnabledAsync(user, true);
lockUserTask.Wait();
var lockDateTask = _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
lockDateTask.Wait();
return lockDateTask.Result.Succeeded && lockUserTask.Result.Succeeded;
}
Controller
public ActionResult LockUser(string email)
{
if (!_userRepository.LockUser(email))
{
throw new ArgumentException("Error");
}
return RedirectToAction("Index");
}
Please refer the following sample code, the UserRepository should like this, add the usermanager via the constructor parameter:
public interface IUserRepository
{
public bool LockUser(string email);
public bool UnlockUser(string email);
}
public class UserRepository : IUserRepository
{
private readonly UserManager<IdentityUser> _userManager;
public UserRepository(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
public bool LockUser(string email)
{
var userTask = _userManager.FindByEmailAsync(email);
userTask.Wait();
var user = userTask.Result;
var lockUserTask = _userManager.SetLockoutEnabledAsync(user, true);
lockUserTask.Wait();
var lockDateTask = _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
lockDateTask.Wait();
return lockDateTask.Result.Succeeded && lockUserTask.Result.Succeeded;
}
public bool UnlockUser(string email)
{
//...
throw new NotImplementedException();
}
}
Then, add the service to the service container:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddScoped<IUserRepository, UserRepository>();
services.AddControllersWithViews();
}
Then, in the MVC controller:
public class HomeController : Controller
{
private readonly IUserRepository _userRepository;
public HomeController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public IActionResult Index(int id)
{
string email = "aa#hotmail.com";
if (!_userRepository.LockUser(email))
{
throw new ArgumentException("Error");
}
return View();
}
The debug screenshot like this:

In ASP.NET Core 2.2, How To Get Base URL in startup service

I've got an asp.net core 2.2 project with a startup.cs service that does an async REST GET call back to my current site and then returns the result (View Component using DI) back to the razor view.
In the service, I want to call "/api/sessions" and not "http://localhost:3433/api/sessions". I know I could use the ~ ta helper if I were inside my razor page to get the base path to the web server, but how can I get that from a service?
Here is my service and relevant code.
From: SessionsService.cs (this is where I don't want http://localhost but just ~/
public class Session
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
public class SessionsService : ISessionsService
{
public async Task<List<Session>> GetSessions(int speakerId)
{
var uri = new Uri("http://localhost:50463/api/sessions");
var httpClient = new HttpClient();
var result = await httpClient.GetStringAsync(uri);
var sessions = JsonConvert.DeserializeObject<List<Session>>(result);
return sessions;
}
}
From: startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISessionsService, SessionsService>();
From: index.cshtml
<vc:speaker-card speaker="#speaker" ></vc:speaker-card>
From: SpeakerCardViewComponent.cs
{
private ISessionsService _sessionsService;
public SpeakerCardViewComponent(ISessionsService sessionsService)
{
_sessionsService = sessionsService;
}
public async Task<IViewComponentResult> InvokeAsync(
Speaker speaker)
{
var sessions = await _sessionsService.GetSessions(101);
speaker.Sessions = sessions;
return View(speaker);
}
}
ANSWER AS SUGGESTED BY KIRK LARKIN FOLLOWS:
public async Task<List<Session>> GetSessions(int speakerId,string baseUrl)
{
var uri = new Uri($"{baseUrl}api/sessions");
var httpClient = new HttpClient();
var result = await httpClient.GetStringAsync(uri);
var sessions = JsonConvert.DeserializeObject<List<Session>>(result);
return sessions;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISessionsService, SessionsService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
public class SpeakerCardViewComponent : ViewComponent
{
private ISessionsService _sessionsService;
private IHttpContextAccessor _httpContextAccessor;
public SpeakerCardViewComponent(ISessionsService sessionsService, IHttpContextAccessor httpContextAccessor)
{
_sessionsService = sessionsService;
_httpContextAccessor = httpContextAccessor;
}
public async Task<IViewComponentResult> InvokeAsync(
Speaker speaker)
{
var isHttps = _httpContextAccessor.HttpContext.Request.IsHttps;
var baseUrl = isHttps ? "https://" : "http://"
+ _httpContextAccessor.HttpContext.Request.Host.Value
+ "/";
var sessions = await _sessionsService.GetSessions(speaker.SpeakerId, baseUrl);
speaker.Sessions = sessions;
return View(speaker);
}
}

GraphQL authentication with Asp.net core using JWT

I am using for GraphQL for .NET package for graphql. But I couldn't understand how can I authentication with JWT in graphql query or mutation.
I read the guide about authorization but I couldn't accomplish.
I need help with GraphQL for .NET authentication.
Any help will be appreciated.
Thanks
The guide is around authorization. The step you're looking for is the authentication and since graphql can be implemented using a ASP.Net API controller, you can implement JWT authentication as you would with any controller.
Here is a sample grapql controller using an Authorize attribute. You could, however, implement this using filter or if you want full control, custom middleware.
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class GraphQLController : ControllerBase
{
private readonly IDocumentExecuter executer;
private readonly ISchema schema;
public GraphQLController(IDocumentExecuter executer, ISchema schema)
{
this.executer = executer;
this.schema = schema;
}
[HttpPost]
public async Task<ActionResult<object>> PostAsync([FromBody]GraphQLQuery query)
{
var inputs = query.Variables.ToInputs();
var queryToExecute = query.Query;
var result = await executer.ExecuteAsync(o => {
o.Schema = schema;
o.Query = queryToExecute;
o.OperationName = query.OperationName;
o.Inputs = inputs;
o.ComplexityConfiguration = new GraphQL.Validation.Complexity.ComplexityConfiguration { MaxDepth = 15};
o.FieldMiddleware.Use<InstrumentFieldsMiddleware>();
}).ConfigureAwait(false);
return this.Ok(result);
}
}
public class GraphQLQuery
{
public string OperationName { get; set; }
public string Query { get; set; }
public Newtonsoft.Json.Linq.JObject Variables { get; set; }
}
In the Startup.cs I have configured JWT bearer token authentication.
Hope this helps.
I myself struggled for two days as well. I'm using https://github.com/graphql-dotnet/authorization now with the setup from this comment (from me): https://github.com/graphql-dotnet/authorization/issues/63#issuecomment-553877731
In a nutshell, you have to set the UserContext for the AuthorizationValidationRule correctly, like so:
public class Startup
{
public virtual void ConfigureServices(IServiceCollection services)
{
...
services.AddGraphQLAuth(_ =>
{
_.AddPolicy("AdminPolicy", p => p.RequireClaim("Role", "Admin"));
});
services.AddScoped<IDependencyResolver>(x => new FuncDependencyResolver(x.GetRequiredService));
services.AddScoped<MySchema>();
services
.AddGraphQL(options => { options.ExposeExceptions = true; })
.AddGraphTypes(ServiceLifetime.Scoped);
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
...
app.UseMiddleware<MapRolesForGraphQLMiddleware>(); // optional, only when you don't have a "Role" claim in your token
app.UseGraphQL<MySchema>();
...
}
}
public static class GraphQLAuthExtensions
{
public static void AddGraphQLAuth(this IServiceCollection services, Action<AuthorizationSettings> configure)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
services.AddTransient<IValidationRule, AuthorizationValidationRule>();
services.AddTransient<IUserContextBuilder>(s => new UserContextBuilder<GraphQLUserContext>(context =>
{
var userContext = new GraphQLUserContext
{
User = context.User
};
return Task.FromResult(userContext);
}));
services.AddSingleton(s =>
{
var authSettings = new AuthorizationSettings();
configure(authSettings);
return authSettings;
});
}
}
public class GraphQLUserContext : IProvideClaimsPrincipal
{
public ClaimsPrincipal User { get; set; }
}
public class MapRolesForGraphQLMiddleware
{
private readonly RequestDelegate _next;
public MapRolesForGraphQLMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// custom mapping code to end up with a "Role" claim
var metadata = context.User.Claims.SingleOrDefault(x => x.Type.Equals("metadata"));
if (metadata != null)
{
var roleContainer = JsonConvert.DeserializeObject<RoleContainer>(metadata.Value);
(context.User.Identity as ClaimsIdentity).AddClaim(new Claim("Role", string.Join(", ", roleContainer.Roles)));
}
await _next(context);
}
}
public class RoleContainer
{
public String[] Roles { get; set; }
}