Custom authorization issue - asp.net-core

I'm testing some custom authorization without the default Entity Framework stuff.
I have created an "ASP.NET Core Web App (Model-View-Controller)" project using "Authentication type" = "Individual Accounts".
In Program.cs I have:
builder.Services.AddTransient<IUserStore<CustomIdentityUser>, CustomUserStore>();
builder.Services
.AddDefaultIdentity<CustomIdentityUser>()
.AddUserStore<CustomUserStore>();
For the moment CustomIdentityUser is just an empty class.
CustomUserStore looks like this:
public class CustomUserStore : IUserStore<CustomIdentityUser>
{
public void Dispose()
{
GC.SuppressFinalize(this);
}
public Task<CustomIdentityUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
// Use dummy user for now
//return new Task<CustomIdentityUser>(() => new CustomIdentityUser());
return new Task<CustomIdentityUser>(() => { throw new Exception("THIS DOES NOT HAPPEN!"); });
}
...
(All other methods from IUserStore currently throws NotImplementedException.)
If I start the application, go the the login page, enter some credentials and click login I can see that FindByNameAsync in CustomUserStore is called. Good, it seems the application actually uses my custom user store to look for the user whose name I just entered.
But that's where my luck ends. The user interface seems to be waiting for the login to complete. The Task returned from FindByNameAsync doesn't seem to be started at all...why? I think that the caller should get CustomIdentityUser instance from it (and then probably call GetPasswordHashAsync in CustomUserStore).

why do you return new task in FindByNameAsync , also CustomUserStore
should be for extending identity properties. in my opinion, creating a service for identity functionality and using dependency injection
create an interface with the name IIdentity.cs
public Task<IdentityResult> UpdateUserAsync(string id , UpdateUserVm updatedUser);
then the implementation class IdentityService
public class IdentityService : IIdentity
{
...
public async Task<IdentityResult> UpdateUserAsync(string id, UpdateUserVm updatedUser)
{
var currentUser = await _userManager.FindByIdAsync(id);
currentUser.PhoneNumber = updatedUser.PhoneNumber;
return currentUser != null ? await _userManager.UpdateAsync(currentUser) : IdentityResult.Failed();
}
}
then register the service
service.AddScoped<IIdentity, IdentityService>();
in controller
public class AccountController : Controller
{
private readonly IIdentity _identityService;
public AccountController(IIdentity identityService) =>
(_identityService) = (identityService);
[HttpPost]
public async Task<IActionResult> UpdateProfile(params)
{
...
await _identityService.UpdateUserAsync(params)
}
}

Related

.NET Core Identity Framework and Telegram Login Widget with no database

I am making a Blazor Server app, which is tied to my Telegram bot. I want to add the ability for the user to login using Telegram Login Widget. I have no plans to add login/password authentication and I therefore don't see any reason to use the database to store anything login-related other than the Telegram User ID.
All of the samples imply using the login-password model along with the database, somewhat like this:
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<AppDbContext>();
Inevitable, this line appears in all of the samples: services.AddEntityFrameworkStores<AppDbContext>();
Here's my question: how do I just put the user's data (after checking the info from Telegram) into app's context, without storing anything in the database? Or if I'm forced to, where do I change the database scheme? Maybe I don't even need to use the Identity framework for this? All I want is for all the pages to have the info about the user, and the authentication happens on Telegram's side, I just get all the info in response and check the hash with my private key. All I want to do after that is put that model into app's context, I'm not even sure I plan on storing the cookie for the user.
To be clear: I already know how to get info from Telegram and check the hash, let's assume after executing some code on a page I already have some User model with some filled out fields
In the end, this is how I did it. While not ideal, this works for me. However, I'd love to get some clarifications from someone, specifically on IUserStore stuff.
I've added Blazored SessionStorage as a dependency to the project
I've registered my own implementations of AuthenticationStateProvider, IUserStore and IRoleStore in Startup.cs like this:
services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
services.AddTransient<IUserStore<User>, CustomUserStore>();
services.AddTransient<IRoleStore<Role>, CustomRoleStore>();
The first line is the most important one. Implementations of IUserStore and IRoleStore don't really matter, but it seems like I have to register them for Identity framework to work, even though I won't use them. All of the methods in my "implementation" are literally just throw new NotImplementedException(); and it still works, it just needs them to exist for the UserManager somewhere deep down, I guess? I'm still a little unclear on that.
My CustomAuthenticationStateProvider looks like this:
public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
private readonly ISessionStorageService _sessionStorage;
private readonly ILogger _logger;
private readonly AuthenticationState _anonymous = new(new ClaimsPrincipal(new ClaimsIdentity()));
public CustomAuthenticationStateProvider(
ILoggerFactory loggerFactory,
ISessionStorageService sessionStorage,
IConfiguration configuration) : base(loggerFactory)
{
_logger = loggerFactory.CreateLogger<CustomAuthenticationStateProvider>();
_sessionStorage = sessionStorage;
// setting up HMACSHA256 for checking user data from Telegram widget
...
}
private bool IsAuthDataValid(User user)
{
// validating user data with bot token as the secret key
...
}
public AuthenticationState AuthenticateUser(User user)
{
if (!IsAuthDataValid(user))
{
return _anonymous;
}
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.FirstName),
new Claim("Username", user.Username),
new Claim("Avatar", user.PhotoUrl),
new Claim("AuthDate", user.AuthDate.ToString()),
}, "Telegram");
var principal = new ClaimsPrincipal(identity);
var authState = new AuthenticationState(principal);
base.SetAuthenticationState(Task.FromResult(authState));
_sessionStorage.SetItemAsync("user", user);
return authState;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var state = await base.GetAuthenticationStateAsync();
if (state.User.Identity.IsAuthenticated)
{
return state;
}
try
{
var user = await _sessionStorage.GetItemAsync<User>("user");
return AuthenticateUser(user);
}
// this happens on pre-render
catch (InvalidOperationException)
{
return _anonymous;
}
}
public void Logout()
{
_sessionStorage.RemoveItemAsync("user");
base.SetAuthenticationState(Task.FromResult(_anonymous));
}
protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState,
CancellationToken cancellationToken)
{
try
{
var user = await _sessionStorage.GetItemAsync<User>("user");
return user != null && IsAuthDataValid(user);
}
// this shouldn't happen, but just in case
catch (InvalidOperationException)
{
return false;
}
}
protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromHours(1);
}
In my Login Blazor page I inject the CustomAuthenticationStateProvider like this:
#inject AuthenticationStateProvider _authenticationStateProvider
And finally, after getting data from the Telegram widget, I call the AuthenticateUser method:
((CustomAuthenticationStateProvider)_authenticationStateProvider).AuthenticateUser(user);
Note, that I have to cast AuthenticationStateProvider to CustomAuthenticationStateProvider to get exactly the same instance as AuthorizedView would.
Another important point is that AuthenticateUser method contains call to SessionStorage, which is available later in the lifecycle of the page, when OnAfterRender has completed, so it will throw an exception, if called earlier.

How to extract ClaimsPrincipal from AuthenticationStateProvider in Transient middleware service

I have a blazor server web application and a .NET Core worker process, these both use a common class for data access (generic unit of work / generic repository).
In the database I would like to log the user names that are inserting or editing records. To do this I want to inject a ClaimsPrincipal to the shared UoW and Repo classes).
So, I would like to be able to extract the current ClaimsPrincipal in a transient service via dependency injection.
For the worker I can inject a ClaimsPrincipal via the following code;
public static IServiceCollection CreateWorkerClaimsPrincipal(this IServiceCollection services, string workerName)
{
Claim workerNameClaim = new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", workerName);
ClaimsIdentity identity = new ClaimsIdentity(
new System.Security.Claims.Claim[] { workerNameClaim },
"My-Worker-Authentication-Type",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"role");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
services.AddTransient<ClaimsPrincipal>(s => principal);
return services;
}
This is working and meets my needs.
For the blazor server web application I need to do something similar.
I believe that the correct way to extract the ClaimsPrincipal is via the AuthenticationStateProvider, however this needs a call to an async method GetAuthenticationStateAsync.
NOTE: I cannot user IHttpContextAccessor as this doesn't work with Azure App Service.
I want something like;
public void ConfigureServices(IServiceCollection services)
{
/// ...
services.AddTransient<ClaimsPrincipal>(); // I think I need to do something here?
/// ...
}
So when I request a ClaimsPrincipal via dependency injection I want to return the user from;
var authState = await AUthenticationStateProvider.GetAuthenticationStateAsync();
return authState.User;
Is this possible?
As is often the way, by working this through into a simple example for a SO post I have found a workable (I think) solution from https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0#implement-a-custom-authenticationstateprovider
NOTE: I'm still not 100% sure if the async init pattern will always resolve the AuthenticationState before the Repository property is called, but its hanging together so far... Just beware of this if you choose to use this code.
I have changed the approach, and instead of trying to resolve ClaimsPrincipal via DI (because AuthenticationStateProvider is not available for a worker process), I have created a custom AuthenticationStateProvider in the worker.
public class WorkerAuthStateProvider : AuthenticationStateProvider
{
private readonly string _workerName;
public WorkerAuthStateProvider(string workerName)
{
_workerName = workerName;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, _workerName),
}, "My-Worker-Authentication-Type");
ClaimsPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
}
and then register this in configureServices to resolve for instances of AuthenticationStateProvider in the worker program.cs file (also passing a custom worker process name, so I can use this on all my workers);
services.AddScoped<AuthenticationStateProvider, WorkerAuthStateProvider>(serviceProvider =>
{
return new WorkerAuthStateProvider(Constants.Logging.RoleNames.MYWORKERNAME);
});
The AuthenticationStateProvider already works in the blazor web apps so this allows me to resolve this correctly, in the constructor for my GenericUnitOfWork pattern for data access on both Web and Workers, for example;
private TDbContext _dbContext;
private readonly ILogger<TEntity> _logger;
private GenericRepository<TEntity, TDbContext> _repository;
private ClaimsPrincipal _user;
private readonly AuthenticationStateProvider _authenticationStateProvider;
public GenericUnitOfWork(TDbContext context, ILogger<TEntity> logger, AuthenticationStateProvider authenticationStateProvider)
{
_dbContext = context;
_logger = logger;
_authenticationStateProvider = authenticationStateProvider;
UserInit = InitUserAsync();
}
/// <summary>
/// Async initialisation pattern from https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
/// </summary>
public Task UserInit { get; private set; }
private async Task InitUserAsync()
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
_user = authState.User;
}
public IGenericRepository<TEntity, TDbContext> Repository
{
get
{
if (_repository == null)
{
// when accessing the repository, we are expecting to pass the current application claims principal
// however the ClaimsPrincipal is resolved using an Async method from the AuthenticationStateProvider.
// In the event that the Async method has not yet completed we need to throw an exception so we can determine
// if a further async code fix is required.
if (_user == null)
{
throw new InvalidOperationException("Async ClaimsPrincipal has not been loaded from the AuthenticationStateProvider");
}
_repository = new GenericRepository<TEntity, TDbContext>(_dbContext, _logger, _user);
}
return _repository;
}
}

OnAuthorizationAsync not being called when creating custom AuthorizeFilter that inherits from AuthorizeFilter

I've created a custom authorize filter which looks like this:
public class BearerTokenAuthorizeFilter : AuthorizeFilter
{
public override async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
await base.OnAuthorizationAsync(context);
if (context.Result is ChallengeResult)
{
// Then return a problem detail
ObjectResult result = new ObjectResult(new ProblemDetails
{
Type = ProblemDetailsTypes.Unauthorized,
Title = ReasonPhrases.GetReasonPhrase(StatusCodes.Status401Unauthorized),
Status = StatusCodes.Status401Unauthorized,
Detail = ProblemDetailsDescriptions.Unauthorized
});
result.ContentTypes.Add(new MediaTypeHeaderValue(new Microsoft.Extensions.Primitives.StringSegment("application/problem+json")));
context.Result = result;
await context.HttpContext.ChallengeAsync();
}
else if (context.Result is ForbidResult)
{
context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
await context.HttpContext.ForbidAsync();
}
}
}
I am registering this filter like this:
services.AddMvcCore(options =>
{
options.Filters.Add<BearerTokenAuthorizeFilter>();
});
I have set the default authentication to be 'Bearer':
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
I have added Authorize attribute on the controller. Whenever I send an unauthorized request to the endpoint my custom filter is never called and I have no idea why? My goal is to return problem details if the request is unauthorized to provide a little bit more information to the consumer than just the status code. Why is my filter not being called?
Try implement IAuthorizationFilter or IAsyncAuthorizationFilter instead of AuthorizeFilter. It work for me. Also I noticed that GetFilter(..) method returns AuthorizeFilter instance directly in AuthorizationApplicationModelProvider when filter class implements AuthorizeFilter. But when filter implements IAuthorizationFilter or IAsyncAuthorizationFilter this method being not called I think that is issue in ASP NET
I have ended up implementing my own IControllerModelConvention class which looks like this:
public class BearerTokenAuthorizeConvention : IControllerModelConvention
{
private AuthorizationPolicy _policy;
public BearerTokenAuthorizeConvention(AuthorizationPolicy policy)
{
_policy = policy;
}
public void Apply(ControllerModel controller)
{
if (controller.Filters.OfType<BearerTokenAuthorizeFilter>().FirstOrDefault() == null)
{
//default policy only used when there is no authorize filter in the controller
controller.Filters.Add(new BearerTokenAuthorizeFilter(_policy));
}
}
}
This will be executed once per controller. I then registered this convention like this:
// Configure application filters and conventions
services.Configure<MvcOptions>(options =>
{
AuthorizationPolicy defaultPolicy = new AuthorizationOptions().DefaultPolicy;
options.Conventions.Add(new BearerTokenAuthorizeConvention(defaultPolicy));
});
At this point every controller I have will be tagged with this custom filter which will call base implementation of AuthorizeFilter. The reason why I wanted to derive from AuthorizeFilter was because I wanted to call the default implementation of Authorize and then handle failed response on my own. I thought I could accomplish this very functionality and somehow still be able to only use Authorize attribute. This doesn't seem to be possible. Unless it is an I'm missing something?

Validating Against credentials Custom Users DB - IResourceOwnerPasswordValidator

I am using Identity Server 4 and implemented the following two interfaces:
IResourceOwnerPasswordValidator: for the purpose of validating user credentials against my custom DB
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private IUserRepository _userRepository;
public ResourceOwnerPasswordValidator(IUserRepository userRepository)
{
this._userRepository = userRepository;
}
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var isAuthenticated = _userRepository.ValidatePassword(context.UserName, context.Password);
//code is omitted for simplicity
}
}
IProfileService: for getting necessary claims
public class ProfileService : IdentityServer4.Services.IProfileService
{
public IUserRepository _userRepository;
public ProfileService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//code is ommitted
}
Then added then necessary Interfaces and dependencies to the services in Startup class. I have also modified the Account Controller by injecting the login service which has the following implementation:
public class LoginService
{
private readonly IUserRepository _userRepository;
public LoginService( IUserRepository userRepository)
{
_userRepository = userRepository;
}
public bool ValidateCredentials(string username, string password)
{
return _userRepository.ValidatePassword(username, password);
}
The AccountController "Login" Action validate for the credentials by calling:
if (_loginService.ValidateCredentials(model.Username, model.Password))
{
var user = _loginService.FindByUsername(model.Username);
await HttpContext.Authentication.SignInAsync(user.Subject, user.Username);
//other code is omitted
When debugging the project the Login Action of AccountContoller is called, then my login service. After validating the user credentials, the consent page is displayed, which trigger the ProfileService GetProfileDataAsync Method.
However, the ResourceOwnerPasswordValidator was never called. Am I Implementing the LoginService in the correct manner or should I replace the IUserRepository injected by IResourceOwnerPasswordValidation then call the "ValidateAsync" method in Login Service ValidateCredentails?
And if that was the case, what is the advantage of passing that to LoginService, since I am already validating users in this way?
I ran into the same problem. The solution was to do just as you have done, but then needed to modify Startup.cs to see it.
// Adds IdentityServer
services.AddIdentityServer()
.AddResourceOwnerValidator<**PasswordAuthentication**>()
NOTE: Make sure you don't add it to the AddIdentity which also has the same .AddResourceOwnerValidator function. It took me awhile. Hope this helps someone.
According to docs:
If you want to use the OAuth 2.0 resource owner password credential
grant (aka password), you need to implement and register the
IResourceOwnerPasswordValidator interface:
Since you don't use password grant, expected behaviour is to not call ResourceOwnerPasswordValidator. For your case you don't need ResourceOwnerPasswordValidator implementation.

OAuth: ASP.NET Web API User.Identity doesn't load claims set by authentication token provider

I am using OAuth bearer authentication, configured like this in Startup.cs:
OAuthBearerAuthenticationOptions oAuthBearerOptions =
new OAuthBearerAuthenticationOptions
{
AccessTokenProvider = new AccessTokenProvider(),
AuthenticationMode = AuthenticationMode.Active
};
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
... where AccessTokenProvider is implemented as:
public class AccessTokenProvider : AuthenticationTokenProvider
{
public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
// Internal logic to get data needed for building identity...
// Create claims identity
ClaimsIdentity identity = new ClaimsIdentity(identityName);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, nameIdentifier));
// Add other claims
// Set claims identity
context.SetTicket(new AuthenticationTicket(identity, new AuthenticationProperties()));
}
}
If I set a breakpoint at the end of ReceiveAsync, I can verify that the identity is built correctly (has claims) and that SetTicket is reached.
But when I try to access the identity from a Web API controller:
public abstract class BaseStorageController : ApiController
{
protected IStorageService StorageService;
protected BaseStorageController(IStorageServiceFactory storageServiceFactory)
{
StorageService = storageServiceFactory.CreateStorageService(User.Identity as ClaimsIdentity);
}
}
... the list of claims on the identity is empty!
What can be causing this?
Side note: I don't know if this is related, but I am using Castle Windsor as an IOC container to inject dependencies into my controllers (in the above case, IStorageServiceFactory). The above seemed to work (claims were not empty) before I added that. However, I'm not using CW to manage anything related to authentication. Here is my CW installer for api controllers:
public class ApiControllerInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<ApiController>().LifestylePerWebRequest());
}
}
I found the answer. It was not related to dependency injection/inversion of control. I'm not sure how I thought it was working prior to adding that.
The issue is similar to what is described here (but in my case the solution is different): User (IPrincipal) not avaliable on ApiController's constructor using Web Api 2.1 and Owin
Basically IPrincipal is not accessible from the constructor of the api controller, which is why there are no claims (the user is not yet authenticated). User.Identity is only accessible from the controller's actions, not the constructor. I changed my base controller implementation to the following to get around this issue:
public abstract class BaseStorageController : ApiController
{
private readonly IStorageServiceFactory _storageServiceFactory;
private IStorageService _storageService;
protected BaseStorageController(IStorageServiceFactory storageServiceFactory)
{
_storageServiceFactory = storageServiceFactory;
}
protected IStorageService StorageService
{
get
{
if (_storageService == null)
{
_storageService = _storageServiceFactory.CreateStorageService(User.Identity as ClaimsIdentity);
}
return _storageService;
}
}
}
Since StorageService is only accessed from controller actions, User.Identity is authenticated and has claims populated by the time that the StorageService getter gets called.
Hope this helps someone!
protected IStorageService StorageService
{
get
{
if (_storageService == null)
{
_storageService = _storageServiceFactory.CreateStorageService(User.Identity as ClaimsIdentity);
}
return _storageService;
}
}
this is not the best approach for implementing DI
It's much better to use constructor injection.
Check Constructor Injection in C#/Unity?
if you are not familliar with Unity, follow this link, very useful:
https://msdn.microsoft.com/en-us/library/dn223671(v=pandp.30).aspx
Regards