access httpContextAccessor claims - userid, UPN, email and name - .net-core

I am using a blazor wasm + core hosted- solution.(.net 6.0)
Here is my project structure:
BlazorApp4.Client
BlazorApp4.Server
BlazorApp4.Shared
I am using windows authentication, since its not supported in wasm, i have enabled windows auth in the server project.
So to access who has logged on I am calling the webapi from the client project.
windowsuser = await httpClient.GetStringAsync("api/wuser");
I have a controller written on the server side
WUserController.cs
namespace BlazorApp4.Server.Controllers
{
[ApiController]
[Route("api/wuser")]
public class WUserController : ControllerBase
{
private readonly ILogger<WUserController> _logger;
WindowsUser? windowsuser = null;
public WUserController(ILogger<WUserController> logger, IHttpContextAccessor httpContextAccessor)
{
windowsuser = new WindowsUser(httpContextAccessor);
_logger = logger;
}
[HttpGet]
public async Task<string> Get()
{
return windowsuser.GetUserName();
}
}
}
WindowsUser.cs
public class WindowsUser
{
private readonly IHttpContextAccessor? _httpContextAccessor;
private readonly string? _userName;
public WindowsUser(IHttpContextAccessor? httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_userName = httpContextAccessor?.HttpContext?.User.Identity?.Name;
var userId =
httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
}
public string GetUserName()
{
return _userName;
}
}
program.cs (server project)
builder.Services.AddHttpContextAccessor();
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
this doesnot work
httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
trying to access upn and email
httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.Upn).Value;
httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.Email).Value;
all are giving null value exception. I guess it is not able to fetch the details.
_userName = httpContextAccessor?.HttpContext?.User.Identity?.Name;
This gives me domainname\username
This is the only property that I am able to get, rest all are null.

Related

How to get an email provider into a logger using DI in ASP.NET Core?

Sorry this is a bit new to me so I don't quite 'get it'.
I already have a logging provider
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
var loggingSection = Configuration.GetSection("Logging");
loggingBuilder.AddFile(loggingSection);
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
I am using the package NReco.Logging.File to define AddFile etc.
I want to make it so that exceptions are emailed to me too. So I followed https://learn.microsoft.com/en-us/dotnet/core/extensions/custom-logging-provider to create a custom logger.
public sealed class EmailLoggerConfiguration
{
public int EventId { get; set; }
public string EmailToSendTo { get; set; }
public IEmailSender EmailSender { get; set; }
}
internal class EmailLoggingProvider : ILoggerProvider
{
private readonly IDisposable? _onChangeToken;
private EmailLoggerConfiguration _currentConfig;
private readonly ConcurrentDictionary<string, EmailLogger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
private readonly IEmailSender emailSender;
public EmailLoggingProvider(
IOptionsMonitor<EmailLoggerConfiguration> config)
{
_currentConfig = config.CurrentValue;
_onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
}
public ILogger CreateLogger(string categoryName) =>
_loggers.GetOrAdd(categoryName, name => new EmailLogger(name, GetCurrentConfig ));
private EmailLoggerConfiguration GetCurrentConfig() => _currentConfig;
public void Dispose()
{
_loggers.Clear();
_onChangeToken?.Dispose();
}
}
internal class EmailLogger : ILogger
{
private readonly string categoryName;
private Func<EmailLoggerConfiguration> getCurrentConfig;
IEmailSender emailSender;
public EmailLogger(string categoryName, Func<EmailLoggerConfiguration> getCurrentConfig)
{
this.getCurrentConfig = getCurrentConfig;
this.categoryName = categoryName;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
public bool IsEnabled(LogLevel logLevel) => !String.IsNullOrEmpty(getCurrentConfig().EmailToSendTo);
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var emailTo = getCurrentConfig().EmailToSendTo;
//var emailServer = getCurrentConfig().EmailSender;
if (!String.IsNullOrEmpty(emailTo) && exception != null)
{
emailSender.SendEmailAsync(emailTo, "Admin exception", exception.ToString());
}
}
}
public static class EmailLoggingExtensions
{
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder)
{
builder.AddConfiguration();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, EmailLoggingProvider>());
LoggerProviderOptions.RegisterProviderOptions<EmailLoggerConfiguration, EmailLoggingProvider>(builder.Services);
return builder;
}
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder,
Action<EmailLoggerConfiguration> configure)
{
builder.AddEmailLogger();
builder.Services.Configure(configure);
return builder;
}
}
You can see that EmailLogger.Log requires emailSender which should be an IEmailSender but I cannot figure out how to get it there using DI.
I realise that you can chain dependencies in DI but ???? I don't see how in this context.
I tried this
loggingBuilder.AddEmailLogger(c =>
{
c.EmailToSendTo = Configuration["Logging:Email:EmailToSendTo"];
c.EmailSender = new AuthMessageSender(????, Configuration);
});
but that didn't help and wouldn't even be right anyway.
In fact, by default, EmailSender is the implementation method of IEmailSender, which is used to call the SendEmailAsync() method. You don't need to go and set c.EmailSender = xxx.
You can consider the following dependency injection approach:
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
public class EmailSender : IEmailSender
{
//...
private readonly ILogger<EmailSender> logger;
public EmailSender(ILogger<EmailSender> logger) {
//...
this.logger = logger;
}
public Task SendEmailAsync(string email, string subject, string message) {
//...
}
}
At this point, IEmailSender will exist as a custom interface instead of inheriting from Microsoft.AspNetCore.Identity.UI.Services.
And you need to register it as a service:
services.AddTransient<IEmailSender, EmailSender>();
Helpful links:
Add ILogger to send email service
Should I use IEmailSender?
Using IEmailSender from Configure() in my Startup.cs file
Hope this will help you better understand IEmailSender and dependency injection.

How to inject my AddDbContext<ContainerContext> into my DAL project (Core 3.1)

I'm using MVC 5, Core 3.1
I have 'AddDbContext' added to my service in Startup.cs.
I then have a Class library core 3.1 project which is my ADO Dal layer.
This is added as a service as well in The ConfigureServices of Startup.cs.
I want to inject the Connection String into the DAL application.
I have:
public partial class ContainerContext : DbContext
{
public ContainerContext()
{
}
public ContainerContext(DbContextOptions<ContainerContext> options) : base(options)
{
}
}
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ContainerContext>(options => options.UseSqlServer(connection));
services.AddDAL();
}
In the Dal project:
public static class ServiceCollectionExtensions
{
// Add parameters if required, e.g. for configuration
public static IServiceCollection AddDAL(this IServiceCollection services)
{
// Register all services as required
services.AddScoped<ILeaseBll, LeaseDal>();
return services;
}
}
The Dal class.
public class LeaseDal : ILeaseBll
{
private string conString;
public LeaseDal(???????)
{
// Some validation for the Context maybe (isNull etc?) throw new ArgumentNullException("conString");
//this.connectionString = conString;
}
How would / should it be done?
Thanks
There is a philosophy change with Dot-Net-Core and Dot-Net-Framework....
public class LeaseDal : ILeaseBll
{
private string conString;
This is not best practice in dot-net-CORE.
You do NOT inject your "connection string" in your concrete DataAccessLayer object.
You inject the db-context.
(and the db-context already has been wired to the Ioc...with its correct connection string)
Something like this:
public interface IDepartmentQueryDomainData()
{
Task<int> GetCountAsync(CancellationToken token);
}
..
public class DepartmentQueryEntityFrameworkDomainDataLayer : IDepartmentQueryDomainData
{
public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";
public const string ErrorMessageMyCoolDbContextIsNull =
"MyCoolDbContext is null";
private readonly ILogger<DepartmentQueryEntityFrameworkDomainDataLayer> logger;
private readonly MyCoolDbContext entityDbContext;
public DepartmentQueryEntityFrameworkDomainDataLayer(
ILoggerFactory loggerFactory,
MyCoolDbContext context
{
if (null == loggerFactory)
{
throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
}
this.logger = loggerFactory.CreateLogger<DepartmentQueryEntityFrameworkDomainDataLayer>();
this.entityDbContext = context ?? throw new ArgumentNullException(
ErrorMessageMyCoolDbContextIsNull,
(Exception)null);
}
public async Task<int> GetCountAsync(CancellationToken token)
{
int returnValue = await this.entityDbContext.Departments.AsNoTracking().CountAsync(token);
this.logger.Log(
new LogEntry(
LoggingEventTypeEnum.Trace,
string.Format(
LogMessages.Count,
returnValue)));
return returnValue;
}
}
You can also "see" this here:
https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
I would never inject the dbContext into a "controller"...(I agree with you that the Dal should be a separate layer)...
but besides that "miscue" on the microsoft example, you do see that you inject the dbContext.
Also see:
https://hovermind.com/aspnet-core/using-dbcontext-with-dependency-injection.html

Remove User with using Guid Id from API

I have problem when I'm making changes about deleting user. I want to make simple it. I want to find my user on context and delete by using UserManager. But, I have an error:
'Guid'cannot impilicitly convert a 'string'.
But, I check the migrations, Id is Guid ?!
public class Delete
{
public class Command : IRequest
{
public Guid Id { get; set; }
}
public class Handler : IRequestHandler<Command>
{
private readonly DataContext _context;
private readonly UserManager<AppUser> _userManager;
private readonly IdentityResult _identityResult;
public Handler(DataContext context, UserManager<AppUser> userManager, IdentityResult identityResult)
{
_context = context;
_userManager = userManager;
_identityResult = identityResult;
}
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
var user = await _userManager.FindByIdAsync(request.Id);
var result = await _userManager.DeleteAsync(user);
var success = await _context.SaveChangesAsync() > 0;
if (success) return Unit.Value;
throw new Exception("Problem saving changes");
}
}
}

How to get User Name from External Login in ASP.NET Core?

I have set up an external login (Google) in my ASP.NET Core application. I am finding it hard to get the User Name / Email after login. I can see the email stored in AspNetUsers table But I don't see User Name anywhere.
I searched over and found this code:
var userId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
But this is giving me userId as is present in table AspNetUsers. ClaimTypes.Email returns null but the value is present in table (probably this email is something else). I want to fetch User Name and User Email. Is it possible?
Do you have access to SignInManager or can you inject it? If yes, then this is how you would access user id (username), email, first & last name:
public class MyController : Microsoft.AspNetCore.Mvc.Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public MyController (
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager
)
{
_userManager = userManager;
_signInManager = signInManager;
}
public async Task<IActionResult> MyAction(){
ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync();
string userId = info.Principal.GetUserId()
string email = info.Principal.FindFirstValue(ClaimTypes.Email);
string FirstName = info.Principal.FindFirstValue(ClaimTypes.GivenName) ?? info.Principal.FindFirstValue(ClaimTypes.Name);
string LastName = info.Principal.FindFirstValue(ClaimTypes.Surname);
}
}
GetUserId extension:
public static class ClaimsPrincipalExtensions
{
public static string GetUserId(this ClaimsPrincipal principal)
{
if (principal == null)
return null; //throw new ArgumentNullException(nameof(principal));
string ret = "";
try
{
ret = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
catch (System.Exception)
{
}
return ret;
}
}

How to Add Claim to ApplicationUser in custom SignInManager

Converting my MVC app to utilize AspNet Identity. I have created an ApoplicationSignInManager class that extends SignInManager
I am overriding CreateIdentityAsyc and I want to add custom claims here without persisting to db so that the values are available for logged in users.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
private readonly IUserService userService;
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager, IUserService userService)
: base(userManager, authenticationManager)
{
this.userService = userService;
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
var myUser = userService.GetUser(user.Id);
var userIdentity = user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
userIdentity.Result.AddClaim(new Claim("MyCustomID", myUser.CutomID.ToString()));
return userIdentity;
}
}
My problem is that the app hangs when executing the AddClaims line. I am guessing this has something to do with a deadlock from async task but I can't figure out how to fix this.
Please give me some guidance.
Try using await before GenerateUserIdentityAsync method.
public async override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
...
var userIdentity = await user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
...
}

Resources