Blazor Server App Authorize Attribute in Data Service does not work - authorization

I have a .NET6 Blazor Server app that has a data service as follows. I thought the [Authorize] attribute will work on it just like it works in controllers. But apparently it does not. Is it possible to use [Authorize] here? How should I implement authorization in this case?
namespace Clinic.Models
{
public interface IData
{
public Task<List<Doctor>> GetAllDoctorsAsync();
public Task<List<Patient>> GetAllPatientsAsync();
}
[Authorize]
public class DataService : IData
{
private readonly IServiceScopeFactory _scopeFactory;
public DataService(IServiceScopeFactory serviceScopeFactory)
{
_scopeFactory = serviceScopeFactory;
}
public async Task<List<Doctor>> GetAllDoctorsAsync()
{
List<Doctor> docs = new List<Doctor>();
using (var scope = _scopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
docs = await context.Doctors.AsNoTracking().ToListAsync();
context.ChangeTracker.Clear();
}
return docs;
}
[Authorize]
public async Task<List<Patient>> GetAllPatientsAsync()
{
List<Patient> patients = new List<Patient>();
using (var scope = _scopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.ChangeTracker.Clear();
patients = await context.Patients
.Include(d => d.Doctor) //get related data as well. primary doc data
.Include(d => d.SecondaryDoctors) //get related data as well.
.AsNoTracking()
.OrderByDescending(d => d.PatientId)
.ToListAsync();
}
return patients;
}
}
}

Related

Can I get data from web api in .net core with another api or api key?

I wrote a controller. I wrote it according to the web api I will use here. But how should I make my own created api?
Do I need to have my own created api where I write with HttpPost? I might be wrong as I am new to this.
public class GoldPriceDetailController : Controller
{
string Baseurl = "https://apigw.bank.com.tr:8003/";
public async Task<ActionResult> GetGoldPrice()
{
List<GoldPrice> goldPriceList = new List<GoldPrice>();
using (var client = new HttpClient())
{
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Sending request to find web api REST service resource GetDepartments using HttpClient
HttpResponseMessage Res = await client.GetAsync("getGoldPrices");
Console.WriteLine(Res.Content);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
var ObjResponse = Res.Content.ReadAsStringAsync().Result;
goldPriceList = JsonConvert.DeserializeObject<List<GoldPrice>>(ObjResponse);
}
//returning the student list to view
return View(goldPriceList);
}
}
[HttpPost]
public async Task<IActionResult> GetReservation(int id)
{
GoldPrice reservation = new GoldPrice();
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.GetAsync("https://apigw.bank.com.tr:8443/getGoldPrices" + id))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResponse = await response.Content.ReadAsStringAsync();
reservation = JsonConvert.DeserializeObject<GoldPrice>(apiResponse);
}
else
ViewBag.StatusCode = response.StatusCode;
}
}
return View(reservation);
}
}
Basically you need these steps:
HttpClient for local API.
HttpClient for external API.
Local API controller.
Inject external client into the local API.
Then inject the local API client into the razor page/controller.
HttpClient for Local API
public class LocalApiClient : ILocalHttpClient
{
private readonly HttpClient _client;
public LocalAPiClient(HttpClient client)
{
_client = client;
_client.BaseAddress(new Uri("https://localapi.com"));
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
// logic to get prices from local api
var response = await _client.GetAsync($"GetGoldPrices?id={id}");
// deserialize or other logic
}
}
HttpClient for External API
public class ExternalApiClient : IExternalHttpClient
{
private readonly HttpClient _client;
public ExternalAPiClient(HttpClient client)
{
_client = client;
_client.BaseAddress(new Uri("https://externalApi.com"));
// ...
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
// logic to get prices from external api
var response = await _client.GetAsync("getGoldPrices?id=" + id))
}
}
Register your clients in startup
services.AddHttpClient<ILocalHttpClient, LocalHttpClient>();
services.AddHttpClient<IExternalHttpClient, ExternalHttpClient>();
Create Local API Controller
and inject the external http client into it
[ApiController]
public class LocalAPIController : Controller
{
private readonly IExternalHttpClient _externalClient;
public LocalAPIController(IExternalHttpClient externalClient)
{
_externalClient = externalClient;
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
var resoponse = await _externalClient.GetGoldPrices(id);
// ...
}
}
Inject the local client into razor page/controller
public class HomeController : Controller
{
private readonly ILocalHttpClient _localClient;
public HomeController(ILocalHttpClient localClient)
{
_localClient = localClient;
}
public async Task<IActionResult> Index(int id)
{
var response = await _localClient.GetGoldPrices(id);
// ...
}
}

Blazor wasm get additional information and add to user claims

I am using identityserver4 for authentication and it's laid out something like this: identity server4 -> Web Api -> Blazor WASM Client(Standalone). everything is getting authenticated and working great. I get the authenticated user claims all the way to the wasm client.
I am now trying to add more claims which come directly from the database. I could have added the claims to the identityserver token but the token gets too big (> 2kb) and then identityserver stops working. apparently this is a known issue.
So iwant to build authorization and trying to keep the jwt token from identityserver small.
in the program.cs file i have a http client like so
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddOidcAuthentication(options =>// Configure your authentication provider options here.
// For more information, see https://aka.ms/blazor-standalone-auth
//builder.Configuration.Bind("Local", options.ProviderOptions);
... provider options
options.ProviderOptions.ResponseType = "code";
options.UserOptions.RoleClaim = "role";
}).AddAccountClaimsPrincipalFactory<CustomAccountClaimsPrincipalFactory>();
await builder.Build().RunAsync();
in the file CustomAccountClaimsPrincipalFactory i have this
public class CustomAccountClaimsPrincipalFactory
: AccountClaimsPrincipalFactory<RemoteUserAccount>
{
private const string Planet = "planet";
[Inject]
public HttpClient Http { get; set; }
public CustomAccountClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
: base(accessor) {
}
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
if (user.Identity.IsAuthenticated)
{
var identity = (ClaimsIdentity)user.Identity;
var claims = identity.Claims.Where(a => a.Type == Planet);
if (!claims.Any())
{
identity.AddClaim(new Claim(Planet, "mars"));
}
//get user roles
//var url = $"/Identity/users/112b7de8-614f-40dc-a9e2-fa6e9d2bf85a/roles";
var dResources = await Http.GetFromJsonAsync<List<somemodel>>("/endpoint");
foreach (var item in dResources)
{
identity.AddClaim(new Claim(item.Name, item.DisplayName));
}
}
return user;
}
}
this is not working as the httpclient is not biolt when this is called and the http client uses the same builder which is building the base http client.
How do i get this to work?
You can create a IProfileService and customise it however you need:
var builder = services.AddIdentityServer(options =>
...
.AddProfileService<IdentityProfileService>();
public class IdentityProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
private readonly UserManager<ApplicationUser> _userManager;
public IdentityProfileService(IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory, UserManager<ApplicationUser> userManager)
{
_claimsFactory = claimsFactory;
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
if (user == null)
{
throw new ArgumentException("");
}
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
//Add more claims like this
//claims.Add(new System.Security.Claims.Claim("MyProfileID", user.Id));
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
Keep the access token small and only include the necessary claims to get past the JwtBearer authentication step.
Then in the API that receives an access token, you can simply create an authorization policy that do lookup the users additional claims and evaluate if he have access or not.
You can do that in the simple policy definitions or the more advanced authorization handlers like the code below:
public class CheckIfAccountantHandler : AuthorizationHandler<CanViewReportsRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
CanViewReportsRequirement requirement)
{
bool result = CallTheCheckIfAccountantService();
if(result)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
A sample requirement can be defined as:
public class CanViewReportsRequirement : IAuthorizationRequirement
{
public int StartHour { get; }
public int EndHour { get; }
public CanViewReportsRequirement(int startHour, int endHour)
{
StartHour = startHour;
EndHour = endHour;
}
}
The important thing is to keep the complexity of the application low and not try to make it harder than it has to be. Just to make the system easy to reason about!

404 Not found when calling Web API using Attribute routing

I am trying to get a call to an attribute route in my ASP.NET Web API 5.27 to work but Im getting a 404 Not Found error. I have searched extensively for this.
My app is a Xamarin.Forms developed with VS2019.
The call works in the browser of real the testing device but not from the app.
Here is my setup:
public class OrdersController : ApiController
{
private readonly OrdersDbContext db = new OrdersDbContext();
// GET: api/Orders/5
[ResponseType(typeof(Order))]
[Route("Order/{Id}")]
[HttpGet]
public async Task<IHttpActionResult> GetOrder(int Id)
{
var order = await db.Orders.Where(w => w.OrderId == Id).Select(s => s)
.FirstAsync();
if (order == null)
{
return NotFound();
}
return Ok(order);
}
}
public class DataService : IDataService
{
readonly HttpClient _client;
public DataService()
{
_client = new HttpClient()
{
BaseAddress = new Uri(Constants.BaseUrl)
};
}
public async Task<Order> GetNewOrder(NewOrderDetails details)
{
var uri = $"Order/1";
var response = await _client.GetStringAsync(uri);
var order = JsonConvert.DeserializeObject<Order>(response);
return order;
}
}

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; }
}

CQRS ValidatorHandler not recognizing FluentValidation validators?

I'm using Web Api 2, Autofac, and MediatR (CQRS). I have a mediator pipeline in place that has pre/post request handlers. That all works fine. I'm trying to hook up Validation now and decorate the pipeline with it.
Here is my Autofac DI code:
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
FluentValidationModelValidatorProvider.Configure(config);
ConfigureDependencyInjection(app, config);
WebApiConfig.Register(config);
app.UseWebApi(config);
}
private static void ConfigureDependencyInjection(IAppBuilder app, HttpConfiguration config)
{
var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());
builder.RegisterAssemblyTypes(typeof(IMediator).Assembly).AsImplementedInterfaces();
builder.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
});
//register all pre handlers
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.As(type => type.GetInterfaces()
.Where(interfacetype => interfacetype.IsClosedTypeOf(typeof(IAsyncPreRequestHandler<>))))
.InstancePerLifetimeScope();
//register all post handlers
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.As(type => type.GetInterfaces()
.Where(interfacetype => interfacetype.IsClosedTypeOf(typeof(IAsyncPostRequestHandler<,>))))
.InstancePerLifetimeScope();
//register all async handlers
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.As(type => type.GetInterfaces()
.Where(interfaceType => interfaceType.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>)))
.Select(interfaceType => new KeyedService("asyncRequestHandler", interfaceType)))
.InstancePerLifetimeScope();
//register pipeline decorator
builder.RegisterGenericDecorator(
typeof(AsyncMediatorPipeline<,>),
typeof(IAsyncRequestHandler<,>),
"asyncRequestHandler")
.Keyed("asyncMediatorPipeline", typeof(IAsyncRequestHandler<,>))
.InstancePerLifetimeScope();
//register validator decorator
builder.RegisterGenericDecorator(
typeof(ValidatorHandler<,>),
typeof(IAsyncRequestHandler<,>),
"asyncMediatorPipeline")
.InstancePerLifetimeScope();
// Register Web API controller in executing assembly.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
//register RedStripeDbContext
builder.RegisterType<RedStripeDbContext>().As<IRedStripeDbContext>().InstancePerRequest();
builder.RegisterType<AutofacServiceLocator>().AsImplementedInterfaces();
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// This should be the first middleware added to the IAppBuilder.
app.UseAutofacMiddleware(container);
// Make sure the Autofac lifetime scope is passed to Web API.
app.UseAutofacWebApi(config);
}
Here is the ValidatorHandler:
public class ValidatorHandler<TRequest, TResponse> : IAsyncRequestHandler<TRequest, TResponse> where TRequest : IAsyncRequest<TResponse>
{
private readonly IAsyncRequestHandler<TRequest, TResponse> _inner;
private readonly IValidator<TRequest>[] _validators;
public ValidatorHandler(
IAsyncRequestHandler<TRequest, TResponse> inner,
IValidator<TRequest>[] validators)
{
_inner = inner;
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request)
{
var context = new ValidationContext(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
return await _inner.Handle(request);
}
}
Here is a sample query:
[Validator(typeof(GetAccountRequestValidationHandler))]
public class GetAccountRequest : IAsyncRequest<GetAccountResponse>
{
public int Id { get; set; }
}
Here is the fluent validation handler:
public class GetAccountRequestValidationHandler : AbstractValidator<GetAccountRequest>
{
public GetAccountRequestValidationHandler()
{
RuleFor(m => m.Id).GreaterThan(0).WithMessage("Please specify an id.");
}
public Task Handle(GetAccountRequest request)
{
Debug.WriteLine("GetAccountPreProcessor Handler");
return Task.FromResult(true);
}
}
Here is the request handler:
public class GetAccountRequestHandler : IAsyncRequestHandler<GetAccountRequest, GetAccountResponse>
{
private readonly IRedStripeDbContext _dbContext;
public GetAccountRequestHandler(IRedStripeDbContext redStripeDbContext)
{
_dbContext = redStripeDbContext;
}
public async Task<GetAccountResponse> Handle(GetAccountRequest message)
{
return await _dbContext.Accounts.Where(a => a.AccountId == message.Id)
.ProjectToSingleOrDefaultAsync<GetAccountResponse>();
}
}
Finally here is the Web Api 2 HttpGet method:
[Route("{id:int}")]
[HttpGet]
public async Task<IHttpActionResult> GetById([FromUri] GetAccountRequest request)
{
var model = await _mediator.SendAsync<GetAccountResponse>(request);
return Ok(model);
}
I put breakpoints all over the place and when I hit this endpoint, the first thing I get into is the GetAccountRequestValidationHandler. Then I get into the ValidatorHandler's constructor. The problem is, the IValidator[] validators parameter to the constructor is always null.
I must be missing something with fluent validation and its registration via Autofac? Any help is much appreciated.
The validator types must be registered in the IoC. Adding the below to your ConfigureDependencyInjection method should do it.
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("ValidationHandler"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();