I'm developing a flutter app using Grpc to connect to the server. Some of the services need extra metadata for authentication, so the first thing that comes to my mind is implementing an interceptor to add the metadata to those requests like this:
class MyClientInterceptor implements ClientInterceptor {
#override
ResponseFuture<R> interceptUnary<Q, R>(ClientMethod<Q, R> method, Q request, CallOptions options, invoker) {
var newOptions = CallOptions.from([options])
..metadata.putIfAbsent('token', () => 'Some-Token');
return invoker(method, request, newOptions);
}
}
But I get Caught error: Unsupported operation: Cannot modify unmodifiable map because CallOptions uses an unmodifiable map.
First question: What is the best practice to add authentication to some of the requests instead of creating the Client stub with those metadata?
Second: How can I copy the metadata from options, modify it and use the modified object?
First question: What is the best practice to add authentication to some of the requests instead of creating the Client stub with those metadata?
Some AUTH libs which I saw use metadata to provide authentication token/key etc.
For example https://github.com/grpc/grpc-dart/blob/master/lib/src/auth/auth.dart#L43
So do not hesitate to add your custom auth header in metadata dict. In can be done via interceptor as you did or via CallOptions:
final resp = await _grpcClient.someApiCall(req,
options: CallOptions(metadata: {'auth': 'your token'}));
Second: How can I copy the metadata from options, modify it and use the modified object?
Just clone previous CallOptions with new value via mergedWith
Second question:
class MyClientInterceptor implements ClientInterceptor {
#override
ResponseFuture<R> interceptUnary<Q, R>(ClientMethod<Q, R> method, Q request, CallOptions options, invoker) {
var newOptions = options.mergedWith(
CallOptions(
metadata: <String, String>{
'token': 'Some-Token',
}
)
);
return invoker(method, request, newOptions);
}
}
Related
I have a API that accepts a submission request (that when accepted to return http code 202 Accepted), for some workflow to approve, and then the caller to retrieve the result later.
The URL should be returned on successful submission and I am trying to use AcceptedAtActionResult with Azure Functions, however I do not understand how to use it as they don't have Controllers per my understanding, but this is one of the parameters.
Regardless and trying to use string.Empty, or the class name EnrollmentFunctions, or path enroll, the library is generating strange paths (in this case pointing to a different function in a different file i.e. location: http://localhost:7071/api/attest/c588484e-8e57-47f6-bf6c-973cfa5b9214?action=GetStatus&controller=enroll).
I am looking to return of the form location: http://localhost:7071/api/enroll/c588484e-8e57-47f6-bf6c-973cfa5b9214 (but avoid hardcoding URLs, both for maintenance and also running in dev mode on local machine).
I believe this would get more complicated if we used a cloud WAF (like cloudflare) on the front-end, in that case I presume would create my own class to build the URL (i.e. https://api-protected-by-waf.mydomain.com/enroll/bf920104-a630-4b58-97cb-cc3ae45c31d3)? I can see this then becoming a configuration issue was looking to avoid on the previous para.
Research points to creating my own IUrlHelper and possibly specifying key:values for retrival via Environment.GetEnvironmentVariable("your_key_here") (for use by my IUrlHelper implementation).
Thanks in advance on how to solve this issue or if i am using the libraries incorrectly and alternate best practice. Code of the Azure functions are below:
public class EnrollmentFunctions
{
[FunctionName("Enroll")]
public async Task<IActionResult> Enroll(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "enroll")] HttpRequest req)
{
var id = Guid.NewGuid();
return new AcceptedAtActionResult("GetStatus", "enroll", new { id = id }, id);
// return new AcceptedAtRouteResult("GetStatus", (new { id }, new { Result = id.ToString() }));
}
[FunctionName("GetStatus")]
public async Task<IActionResult> GetStatus(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "enroll/{id}")] HttpRequest req)
{
return new OkObjectResult("OK");
}
}
I'm migrating an application from NancyFx to Kestrel in ASP.NET Core 6.
In Nancy, you could specify the Accept value in the URI. For example, these Uris:
http://localhost:5000/my/resource.json
http://localhost:5000/my/resource.protobuf
http://localhost:5000/my/resource.xml
Would be the equivalent of setting the Accepts header to application/json, application/protobuf or application/xml respectively.
Does this exist in Kestrel? I remember finding one example, long ago, of regex-ing the route and doing it somewhat manually. But
I can't find that post again, and
If I have to do that, I'm not sure I want to :)
Is there a way to configure this behavior in ASP.NET Core 6?
The object returned from my handler in the controller is already capable of being serialized to json/xml/whatever. I just need to check the URI to set the content-type of the response so the correct formatter will be invoked.
At the moment, I have a client that will speak to both Nancy and Kestrel and it was written to use the URI to get the type. I'm fine to rewrite/update the client so it will use the Accept header. But getting the URI method to work will make the initial integration easier and a refactor to use the headers can come next.
I created a very simple middleware that reads the accept value from the query string and sets the Accept header to the request:
public class AcceptHeaderFromQueryString
{
private readonly RequestDelegate _next;
public AcceptHeaderFromQueryString(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var accept = context.Request.Query["accept"];
if (!string.IsNullOrEmpty(accept))
{
context.Request.Headers.Accept = accept;
}
await _next(context);
}
}
Register the middleware:
app.UseMiddleware<AcceptHeaderFromQueryString>();
I added [Produces(MediaTypeNames.Application.Json, MediaTypeNames.Application.Xml)] attribute to my api controller action (this step is not required):
[HttpGet]
[Produces(MediaTypeNames.Application.Json, MediaTypeNames.Application.Xml)]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
Finally I added support for xml serialization in Program.cs:
builder.Services.AddControllers()
.AddXmlDataContractSerializerFormatters();
Then I tried these urls and they both gave appropriate response:
https://localhost:7258/weatherforecast?accept=application/json
https://localhost:7258/weatherforecast?accept=application/xml
You possibly want the [Consumes] attribute. This allows you to specify a controller action that only gets called from a route of the specified content type.
Obviously this is not using the Accepts header but the content type of the request.
UPDATE
Some more digging showed that thrown Exceptions were dropped and the actual problem is that an injected UriInfo could not be resolved in the AsyncResponse's thread!
Accessing #Context UriInfo uriInfo; during AsyncResponse.resume() gives the following LoggableFailure's message:
Unable to find contextual data of type: javax.ws.rs.core.UriInfo
ORIGINAL
According to RFC 7231 HTTP/1.1 Semantics and Control, a POSTshould return 201 CREATED and supply the new resource's location in the header:
the origin server
SHOULD send a 201 (Created) response containing a Location header
field that provides an identifier for the primary resource created
(Section 7.1.2) and a representation that describes the status of the
request while referring to the new resource(s).
When writing a synchronous REST Server, the javax.ws.rs.core.Responseoffers the Response.created() shorthand which does exactly that.
I would save the new entity, build an URI and return
return Response.created(createURL(created)).build();
However, when I switch to an asynchronous approach utilizing a
#Suspended javax.ws.rs.container.AsyncResponse
the HTTP request on the client will hang infinitely:
#POST
public void createUser(#Valid User user, #Suspended AsyncResponse asyncResponse) {
executorService.submit(() -> {
User created = userService.create(user);
asyncResponse.resume(
Response.created(createURL(created)).build()
);
});
}
Through trial-and-error I found out that the modified location header is responsible.
If I return my entity and set the 201 Created, without touching the header, the request will eventually resolve:
#POST
public void createUser(#Valid User user, #Suspended AsyncResponse asyncResponse) {
executorService.submit(() -> {
User created = userService.create(user);
asyncResponse.resume(
Response.status(Status.CREATED).entity(created).build() //this works
//Response.created(createURL(created)).build()
);
});
}
So what's the problem? Am I misunderstanding the concepts?
I am running RestEasy on GlassFish4.1
If you need more information, please comment!
edit
As soon as I change any link or the header, the request will hang.
In case anyone ever has the same problem:
The problem was that I created the location header through an injected #Context UriInfo uriInfo using its .getAbsolutePathBuilder().
The approach was working in a synchronous server because the thread which accessed the UriInfo still had the same Request context.
However, when I switched to an async approach, the underlying Runnable which eventually had to access uriInfo.getAbsolutePathBuilder() was NOT within any context - thus throwing an exception which halted further execution.
The workaround:
In any async method which should return a location header, I .getAbsolutePathBuilder() while still within the context. The UriBuilder implemantion can then be used within the async run:
#POST
public void createUser(#Valid User user, #Suspended AsyncResponse asyncResponse) {
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
executorService.submit(() -> {
User created = userService.create(user);
asyncResponse.resume(
Response.created(createURL(ub, created)).build()
);
});
}
private URI createURL(UriBuilder builder, ApiRepresentation entity) {
return builder.path(entity.getId().toString()).build();
}
I would try to create an API Rest client for a code that I have in a GWT project.
In order to arrive to the server and obtain a response I need to attach some custom headers in my request.
I saw, there were some bugs some years ago when write headers in the request.
Actually, I have this code, and I don't know exactly where and when put my custom header in the request.
final Map<String, String> headers = new HashMap<String, String>();
headers.put("X-USER", "super_admin_key");
Resource resource = new Resource("http://localhost:9998/api/v1/", headers);
ServicesAPI service = GWT.create(ServicesAPI.class);
((RestServiceProxy)service).setResource(resource);
REST.withCallback(new MethodCallback<String>(){
#Override
public void onFailure(org.fusesource.restygwt.client.Method method, Throwable exception) {
domainsCombo.addItem("ERROR");
}
#Override
public void onSuccess(org.fusesource.restygwt.client.Method method, String response) {
domainsCombo.addItem("ok");
}
}).call(service).getServices();
Add headers to the resource object creation is one of the options, but it doesn't work.
Any sugestion?
Thanks.
I have REST service requirements in which some calls require authentication and some don't. Absolutely no state is used, as the calls are all independent from one another. I have put something together which seems to work, but is this the right way to go about not using sessions?
This question is kind of related to my WCF question which is answered here.
Firstly I registered the authentication method:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}
));
I then attribute the respective calls (or service or DTO) with the Authenticate attribute:
[Authenticate]
public HelloResponse Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name + " with POST & Auth"};
}
I inherit from the BasicAuthProvider class which does the authentication:
public class CustomCredentialsAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return userName == "dylan" && password == "abc123";
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
session.IsAuthenticated = true;
//Important: You need to save the session!
authService.SaveSession(session, new TimeSpan(0,0,10));
}
}
As you can see, I do save the session but it times out after 10 seconds. This is the part that I'm sure can potentially be done better. It seems to work nicely though.
Is there a better way of doing what I'm trying to accomplish?
Is there also any way, due to the sessionless nature of these services, to remove the Auth, AssignRoles and UnassignRoles methods?
If you wanted to keep using ServiceStack's Authentication and Session support you could just add a response filter that clears the user session after the service is executed, e.g:
this.ResponseFilters.Add((req, res, dto) => req.RemoveSession());
Basically after each request is executed it clears the session, so no record of them having authenticated exists.
Otherwise you can just skip using ServiceStack's Authentication completely and just provide your own via RequestFitlers of FilterAttributes (which is essentially what SS Auth does under the hood).