trying to Azure AD authentication with gRPC-Web using protobuf-net - asp.net-core

I am trying to Azure AD authentication with gRPC-Web in a blazor webassembly app. I am using protobuf-net to help me with the serialization. I am not sure how to pass the token to have the server side recognize it. this is what I have:
var headers = new Metadata
{
{ "Authorization", $"Bearer {Token}" }
};
and, I am sending that as a parameter in the method I want to consume
var result = await Client.CreateCustomer(this.customer, headers);
This is how the service is injected:
builder.Services.AddTransient(services =>
{
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
var channel = Grpc.Net.Client.GrpcChannel.ForAddress("****", new GrpcChannelOptions { HttpClient = httpClient });
return channel.CreateGrpcService<Application.Services.ICustomerService<ServerCallContext>>();
});
This is how the service is published:
endpoints.MapGrpcService<CustomerService>().RequireAuthorization().EnableGrpcWeb()
and, this is the implementation:
public class CustomerService : ICustomerService<ServerCallContext>
{
[Authorize]
public async ValueTask<Customer> CreateCustomer(Customer customerDTO, ServerCallContext context)
{****}
}
the error I am getting is cannot convert from 'Grpc.Core.Metadata' to 'Grpc.Core.ServerCallContext' which is kind of obvious.
The reference I have found uses Metadata but is ServerCallContext the one I am supposed to use https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/metadata so what I am missing, what I am doing wrong, how to properly use both using protobuf-net?

It looks like the problem here is that you're using ServerCallContext in the method signature; the underlying gRPC core has separate client/server context APIs, but this is not amenable to use on an agnostic interface, and as such, protobuf-net.Grpc unifies these two APIs, via CallContext. So: instead of:
async ValueTask<Customer> CreateCustomer(Customer customerDTO, ServerCallContext context)
for the signature, consider:
async ValueTask<Customer> CreateCustomer(Customer customerDTO, CallContext context)
or
async ValueTask<Customer> CreateCustomer(Customer customerDTO, CallContext context = default)
The CallContext API exposes the common server-side and client-side APIs (headers, cancellation, etc) in a single way, or you can use (for example) context.ServerCallContext to get the server-specific API if needed (this will throw an exception if used on a client-context). For client-side usage, a CallContext can be constructed from a CallOptions, which is the core gRPC client-side API, for example:
var result = await service.CreateCustomer(customer, new CallOptions(headers));
I'm open to the idea of allowing CallContext to be created directly from Metadata / CancellationToken etc (allowing var result = await service.CreateCustomer(customer, headers);) - but it doesn't seem essential.

Related

Http Client registration based on http header received from a middleware

I am having below situation : I am using web api core 3.1 framework (c#)
I am using typed httpclient registered in the startup . while registering typed client on startup, i am not able to provide the base URL and credentials because I am getting thru a service called configread and it reads the data from the header , which will be only available when one of our middle ware runs and sets it.
in my case base address, user id and passwords are coming from a service call but service calls depends on the request header (httpContext object). in the configureService methods , request context is not available.
Right now i am having trouble to get the httpClient from the startup.
Any guidance would be appreciated.
Update1:
I am adding a typed client as below
service.AddHttpClient<IAgencyServiceAgent,AgencyServiceAgent> (GetAgencyAgentHttpClient()).
ConfigurePrimaryHttpMessageHandler(GetAgencyHttpMessageHandler()) private Action<HttpClient> GetAgencyAgentHttpClient ()
{
var configUrl = Environment.GetEnvironmentVariable(ConfigConstants.CONFIGSERVICE URL)
return httpClient => {
// Here the base address is availble thru another service // which accept the data from the httpContext and based on the values / It pulls the base address and request header etc...
}
}
Update2:
I am having difficulty in setting this httpclient in the startup beacuse baseUrl and other info depends on the request object. For ex: i am reading a request header called DEV1 and passing it to another service , then it will return me the base address and credentials needed then after i can set the http client My questions are how do go about it . When httpClient configurations are depend on the httpContext object .. then how we should register and use it Thanks
According to your description, I suggest you could try to build ServiceProvider inside your GetAgencyAgentHttpClient method and use GetService method to get which service you want to use.
More details, you could refer to below codes:
services.AddHttpClient("hello", c =>
{
//Build service provider
ServiceProvider serviceProvider = services.BuildServiceProvider();
//Get the ICurrentUserService
var currentUserService = serviceProvider.GetService<ICurrentUserService>();
//Use ICurrentUserService GetIPaddress method
var re= currentUserService.GetIPaddress();
c.BaseAddress = new Uri("http://localhost:5000");
}).AddTypedClient(c => Refit.RestService.For<IHelloClient>(c)); ;
Result:
If you want to check get the http header from current httprequest, you could try to get the httpcontext accessor in the service.
More details, you could refer to below codes:
services.AddHttpClient("hello", c =>
{
ServiceProvider serviceProvider = services.BuildServiceProvider();
//var currentUserService = serviceProvider.GetService<ICurrentUserService>();
//var re= currentUserService.GetIPaddress();
var httpcontext = serviceProvider.GetService<IHttpContextAccessor>();
var re = httpcontext.HttpContext.Request.Headers.ToList();
c.BaseAddress = new Uri("http://localhost:5000");
}).AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
Result:

How can I use Asp.Net Core 2.0's in-memory TestServer class for Integration Tests when my API requires an Authorization Token?

I am working on an ASP.NET Core 2.0 Web API and I want to do some integration tests using ASP.NET Core's TestServer class. I am using xUnit as my testing framework so I have created a TestServerFixture class that creates the in-memory TestServer instance and then use the TestServer's .CreateClient() to create the HTTPClient instance.
My Web API requires an OAuth2.0 Access Token from my Azure AD. I set this up using this code in my Startup.cs, ConfigureServices method:
// Add Azure AD OAUTH2.0 Authentication Services
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
and in my controllers, I have the [Authorize] attribute on the class.
So for my Integration Tests setup, I have a method in my TestServerFixture that obtains a valid token from Azure AD and I add it to my client request header as follows;
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _testServerFixture.GetAccessToken());
When I debug my integration test, I can see that the request does contain a valid access token but I am still getting a 401 Unauthorized from the API when I run my Integration Test.
After doing some digging I found several resources that talk about a similar issue with TestServer, but related to Authentication rather than Authorization, as I am experiencing. Here are links to these resources;
https://medium.com/#zbartl/authentication-and-asp-net-core-integration-testing-using-testserver-15d47b03045a
How do I integration test a ASP 5/Core Web API with [Authorize] Attributes
http://geeklearning.io/how-to-deal-with-identity-when-testing-an-asp-net-core-application/
These all talk about assigning a ClaimsPrincipal to the context.user using custom middleware. Since this is based upon Authentication rather than Authorization, I am not sure if I can do something similar for my Access Token.
I do know that in my API, I can access the HTTPContext.User and pull out the AppId value, which is part of the Access Token so it would seem that Authentication and Authorization both use the Context.User.
So, before I burn time building up my own custom middleware for this purpose, I wanted to see if anyone has already addressed this issue or perhaps are aware of a NuGet that does what I need.
EDIT - SOLUTION
I am showing this in case anyone else runs into this issue.
I ended up building the middleware that Zach Bartlett presented in his blog , but making the following changes.
public class AuthenticatedTestRequestMiddleware
{
#region Class Variables
private const string TestingAccessTokenAuthentication = "TestingAccessTokenAuthentication";
private readonly RequestDelegate _next;
#endregion Class Variables
#region Constructor(s)
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
#endregion Constructor(s)
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Integration-Testing"))
{
if (context.Request.Headers.Keys.Contains("Authorization"))
{
var token = context.Request.Headers["Authorization"].First();
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Authentication, token)
}, TestingAccessTokenAuthentication);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
context.User = claimsPrincipal;
}
}
await _next(context);
}
}
There were one interesting "Gotcha".
In Zach's blog he had the code;
public const string TestingHeader = "X-Integration-Testing";
at the top of his middleware and then references the TestingHeader in the test for the key in the header collection like this;
if (context.Request.Headers.Keys.Contains(TestingHeader)
Doing it this way was failing for me until I put the string literal instead of the variable into the .Contains() clause.
Now, my integration test is passing with a 200 OK response. :)
I was able to find a solution following Zach Bartlett's blog post, and making some small changes to make it pertain to the Authentication header. The code is shown as an edit in my original post above.

How to extend IdentityServer4 workflow to run custom code

I have a basic Identityserver4 implementation based on the quick start sample.
In my startup I have the following:
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
}
public void Configure(IApplicationBuilder app)
{
...
app.UseIdentityServer();
}
I want to extend the IdentityServer4 workflow so that after the access token is generated I can run business logic (based on the claims in the access token) and modify the response send to the calling client. I tried creating a .NET core middleware but it seems the IdentityServer middleware short-circuits the rest of the pipeline (no middleware place after the UseIdentityServer will be executed).
Are they any extension method in Identityserver4 that I can use to always modify the response issued by IdentityServer4? I am using the credentials grant. Essentially I want to run some business logic to modify the response send to the client once IdentityServer4 is done with its workflow
Unfortunately, there is no way to do that.
When you request any IdentityServer endpoint, IdentityServer middleware short-circuits the rest of the pipeline.
You can check source code:
IdentityServerMiddleware class.
I believe it was done for a reason. But if you really need to modify the response, you have at least three options:
Create a fork and remove return operator from
IdentityServerMiddleware Invoke method (be careful to short-circuit the rest of the pipeline adding return into your last middleware).
Create your own IdentityServerMiddleware, IdentityServerApplicationBuilderExtensions implementations and use
them instead of default.
Place your middleware before the UseIdentityServer. Your middleware should look like this:
public ResponseBodyEditorMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// get the original body
var body = context.Response.Body;
// replace the original body with a memory stream
var buffer = new MemoryStream();
context.Response.Body = buffer;
// invoke the next middleware from the pipeline
await _next.Invoke(context);
// get the body as a string
var bodyString = Encoding.UTF8.GetString(buffer.GetBuffer());
// make some changes
bodyString = $"The body has been replaced!{Environment.NewLine}Original body:{Environment.NewLine}{bodyString}";
// update the memory stream
var bytes = Encoding.UTF8.GetBytes(bodyString);
buffer.SetLength(0);
buffer.Write(bytes, 0, bytes.Length);
// replace the memory stream with updated body
buffer.Position = 0;
await buffer.CopyToAsync(body);
context.Response.Body = body;
}

Need help debugging JwtSecurityTokenHandler (web api owin auth / resource servers)

I am desperately trying to find out why I keep getting a 401 response from my resource server:
Scenario:
Console program running an authorization server.
Console program running a resource server.
Console program using both (client).
I have no problem getting the token, and it validates with expected content at jwt.io. Further, when submitting the base64 encoded secret used to create the token, the site also validates the token string.
I have tried every possible combination of suppressing and/or adding HostAuthentication defaults/filters; I have used UseCors (AllowAll) before and after both auth and webapi use-calls; I have tried juuuuust about anything!
This is my configure auth:
private void ConfigureAuth(IAppBuilder app, HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter("Bearer"));
var issuer = ConfigurationManager.AppSettings["tokenIssuer"];
var audience = "MyEnergy"; //TODO: audience should be taken from somewhere else!!
var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]); //TODO: no configuration manager here!
var jwtBearerOptions = new JwtBearerAuthenticationOptions()
{
TokenHandler = new testdumbass(),
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AuthenticationType = "JWT",
AllowedAudiences = new[] {audience},
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)}
};
app.UseJwtBearerAuthentication(jwtBearerOptions);
}
and here is my Configuration and ConfigureWebApi:
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
ConfigureAuth(app, config); //using oauth
ConfigureWebApi(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private void ConfigureWebApi(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"Default",
"{controller}/{id}",
new {id = RouteParameter.Optional});
}
So, I tried to implement a custom JwtSecurityTokenHandler (testdumbass class) and just overrided everything I could, calling base operations. From what I can see, the default implementation has NO problem reading the token (all the expected values are there).
So how can I test the validation?
The following three methods are called (in that order):
ReadToken
CanReadToken
ValidateToken (out validatedToken)
*ValidateSignature
*ReadToken
*CanReadToken
*ValidateIssuerSecurityKey
*ValidateLifetime
*ValidateAudience
*ValidateIssuer
*CreateClaimsIdentity
ValidateToken finishes
Now the out parameter looks fine. SecurityKeys however are 0 and Id is null (any relevance?)
The inner JwtSecurityToken has a signing key, where the 32 byte array matches what I would expect.
If I look at non-public members allthe 4 rawXXX field have values (Data, Header and Payload as expected (dunno what to make of rawSignature))
I am using "https://localhost" as issuer, which can also be read from the validated token.
After all of this, I have overriden OnAuthorizationAsync in my own custom AuthorizeAttribute. Here I call the base implementation (the one in AuthorizeAttribute) and the actionContext always fails with 401.
I must admit I really cannot figure out WHY!
I found out what the problem was:
I took these two lines from here:
private void ConfigureAuth(IAppBuilder app, HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter("Bearer"));
and moved them here:
private void ConfigureWebApi(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter("Bearer"));
config.MapHttpAttributeRoutes();
This would result in the correct context with correct identity in the AuthorizeAttribute validation (in the HttpActionContext sent as parameter).
What I dont get, is why changes in the config matter before using it in the UseWebApi call?

Async REST Services using WCF WebApi

I want to know what is the opinion of you fellow Developers regarding WCF WebApi services.
In an N-tier application we can have multiple layers of services. We can have services consuming data from external services. In that scenario its worth to create Async Rest Services using WCF 4.0.
public interface IService
{
[OperationContractAttribute(AsyncPattern = true)]
IAsyncResult BeginGetStock(string code, AsyncCallback callback, object asyncState);
//Note: There is no OperationContractAttribute for the end method.
string EndGetStock(IAsyncResult result);
}
But with the release of WCF WebApi this approach is still required? to create async services?
How to host them in IIS/WAS/Self Hosting
looking forward for suggestion and comments.
Well What i feel,In order to create asynchronous operations in the latest WCF WebAPIs (preview 6) I can still use same pattern (Begin/End), but I can also use the Task programming model to create asynchronous operations, which is a lot simpler.
One example of an asynchronous operation written using the task model is shown below.
[WebGet]
public Task<Aggregate> Aggregation()
{
// Create an HttpClient (we could also reuse an existing one)
HttpClient client = new HttpClient();
// Submit GET requests for contacts and orders
Task<List<Contact>> contactsTask = client.GetAsync(backendAddress + "/contacts").ContinueWith<Task<List<Contact>>>((responseTask) =>
{
return responseTask.Result.Content.ReadAsAsync<List<Contact>>();
}).Unwrap();
Task<List<Order>> ordersTask = client.GetAsync(backendAddress + "/orders").ContinueWith<Task<List<Order>>>((responseTask) =>
{
return responseTask.Result.Content.ReadAsAsync<List<Order>>();
}).Unwrap();
// Wait for both requests to complete
return Task.Factory.ContinueWhenAll(new Task[] { contactsTask, ordersTask },
(completedTasks) =>
{
client.Dispose();
Aggregate aggregate = new Aggregate()
{
Contacts = contactsTask.Result,
Orders = ordersTask.Result
};
return aggregate;
});
}
[WebGet(UriTemplate = "contacts")]
public Task<HttpResponseMessage> Contacts()
{
// Create an HttpClient (we could also reuse an existing one)
HttpClient client = new HttpClient();
// Submit GET requests for contacts and return task directly
return client.GetAsync(backendAddress + "/contacts");
}
WCF Web API comes with an completely async HttpClient implementation and you can host in IIS and also completely sefhost.
For a async REST "service" scenario please read "Slow REST"