Comments (9)
Not started yet, still thinking on what I want and how sane it is, also less free time to work on this as I had hoped.
I was rather thinking of using the refitter [Headers("Authorization: x")]
mechanism. The way I was thinking of implementing it was as a commandline option for refitter: --usesecurityscheme "jwt-auth"
where "jwt-auth" is one of the security schemes available in the openapi specification you are generating for.
Generally you will only use one type of authentication in your project anyways and generally all the authorized calls will support all the of the possible authentication methods. That is an assumption on my side but I think it's a sane assumption.
Then for each method that supports the given security-scheme, I will generate a method with the method level attribute: [Headers("Authorization: x")]
where x is the right type for that security scheme, either bearer or basic, etc.
Each method that has no security schemes will not have the attribute,
Each method that has other security schemes but not the one you chose will not be generated? (Will this ever happen?)
Then you can configure the refit library to register a callback for authorization header input. So you can do this without attaching a generic header manipulator/adder class to the http client. But rather some lightweight lambda.
to clariy my last paragraph; an except from the refit github page.
Bearer Authentication
Most APIs need some sort of Authentication. The most common is OAuth Bearer authentication. A header is added to each request of the form: Authorization: Bearer . Refit makes it easy to insert your logic to get the token however your app needs, so you don't have to pass a token into each method.
- Add [Headers("Authorization: Bearer")] to the interface or methods which need the token.
- Set AuthorizationHeaderValueGetter in the RefitSettings instance. Refit will call your delegate each time it needs to obtain the token, so it's a good idea for your mechanism to cache the token value for some period within the token lifetime.
from refitter.
Just started using refitter recently. Thanks for this software! That said, is there any love or ETA towards this (no commentary for ~8 months)? Every time I regenerate my bindings, I must manually add the attribute to my secure endpoints (only a subset requires authorization) and I worry that I, or a teammate will miss endpoints.
[Headers("Authorization: Bearer")]
from refitter.
@Roflincopter first of all, thank you for bringing this up and for your interest in this
To ensure I understand, are you looking into generating code based on security schemes defined in the OpenAPI specification file?
I have only used Refit with bearer token authorization and for this approach, I keep security concerns out of the API itself. What I like about Refit is that they allow the developer to easily customize the instance of the HttpClient used by the generated code.
I mostly work on the backend of systems and what I normally do is implement HttpClient message handlers for acquiring an access token, as a confidential client using a client/secret persisted securely in some arbitrary secret store (e.g. Azure Keyvault), or using the managed identity service and the DefaultAzureCredentials class from Azure.Identity
Refit provides convenient extension methods to IServiceCollection
in the Refit.HttpClientFactory library. Configuring the Refit client becomes as simple as:
static IServiceCollection ConfigureApiClient(this IServiceCollection services)
{
services.AddTransient<ApiAuthenticationHandler>();
services
.AddRefitClient<IApiClient>()
.ConfigureHttpClient(c => c.BaseAddress = GetApiBaseAddress())
.AddHttpMessageHandler<ApiAuthenticationHandler>();
return services;
}
where the authentication handler might look something like this:
class ApiAuthenticationHandler : DelegatingHandler
{
private readonly TokenRequestContext context;
public ApiAuthenticationHandler()
{
context = new TokenRequestContext(new[] { "https://api.foo.com/dev/bar/.default" });
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Headers.Authorization = await GetTokenAsync(cancellationToken);
var response = await base.SendAsync(request, cancellationToken);
return response;
}
private async Task<AuthenticationHeaderValue?> GetTokenAsync(CancellationToken cancellationToken)
{
var credentials = new DefaultAzureCredential(AzureCredentialOptions.CreateDefault());
var token = await credentials.GetTokenAsync(context, cancellationToken);
return new AuthenticationHeaderValue("Bearer", token.Token);
}
}
In most, if not all cases, I would also configure things like HTTP telemetry logging and retry policies with Polly at the HttpClient level
static IServiceCollection ConfigureApiClient(this IServiceCollection services)
{
services.AddTransient<ApiAuthenticationHandler>();
services
.AddRefitClient<IApiClient>()
.ConfigureHttpClient(c => c.BaseAddress = GetApiBaseAddress())
.AddHttpMessageHandler<ApiAuthenticationHandler>()
.AddHttpMessageHandler<TelemetryDelegatingHandler>()
.AddPolicyHandler(
HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
Backoff.DecorrelatedJitterBackoffV2(
TimeSpan.FromSeconds(1),
6)))
return services;
}
Where the telemetry handler might look something like this:
class TelemetryDelegatingHandler : DelegatingHandler
{
private readonly ITelemetryClient telemetryClient;
public TelemetryDelegatingHandler(ITelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
return await WriteTelemetry(request, response);
}
private async Task<HttpResponseMessage> WriteTelemetry(
HttpRequestMessage request,
HttpResponseMessage response)
{
try
{
var telemetry = new TraceTelemetry(
"Outbound HTTP Request",
!response.IsSuccessStatusCode ? SeverityLevel.Error : SeverityLevel.Verbose);
// Extract request and response details to trace telemetry properties
telemetryClient.TrackTrace(telemetry);
}
catch (Exception e)
{
telemetryClient.TrackException(e);
}
return response;
}
}
Sorry for the excessive explanations and examples. You probably already know all that, but since I can't really know I wrote it anyway
So back to the point, are you interested or looking into generating code that redefines headers in the refit interface? Like this:
[Headers("Authorization: Basic YmFzaWMtYXV0aG9yaXphdGlvbi1pczpnZW5lcmFsbHktYS1iYWQtaWRlYQ==")]
public interface ISomeApi
{
[Get("/things/{id}")]
Task GetSomething(string id);
}
or are you perhaps after generating the boilerplate code for configuring the HttpClient that Refit uses, like in the first example?
As for implementation details, your actual inquiry (again, sorry that I digress), I recently introduced the IRefitGeneratorInterface which is designed to have multiple implementations based on the RefitGeneratorSettings and how the code should be generated
If we are to support generating code based on security schemes then implementing IRefitInterfaceGenerator
would be the best place to do so. It's of course very important that we respect all the settings configured in the RefitGeneratingSettings
instance so that we are not breaking any existing functionality
It might also be interesting to generate boilerplate code and convenience extension methods to IServiceCollection, but that will also need to be configured from settings since not everyone using Refitter is building server systems
Thanks for bringing this up again @Roflincopter
from refitter.
@all-contributors please add @Roflincopter for ideas
from refitter.
I've put up a pull request to add @Roflincopter! 🎉
from refitter.
I just saw the Authorization attribute in the refit documentation and thought i would be nice to add it to the Interface generator. But as soon as I started thinking about how to implement it there was no clear approach how to because then you would either have to configure the refit http client with right options, which might not be very clear that you have to do. Or you generate methods with explicit parameters for each security scheme you support which leads to an increase of methods on the interface.
Task PostSomething([Body] object body, [Authorize("Bearer")] string token, CancellationToken cancellationToken = default);
Implementing it with HttpMessageHandlers seems like an alright choice, I could still take a look at adding the "Authorization" attributes to the methods, so you have to option to configure it in the client without writing an HttpMessageHandler for it.
[Headers("Authorization: Bearer")]
But then I would have to detect and emit the right attributes, and I have to make sure I don't break the existing method of writing your own HttpMessageHandler. And I don't have a way to test OAuth authentication methods.
from refitter.
Have you started on this @Roflincopter ?
I was thinking that it would be cool to have a --use-http-client-factory "[base url]" --azure-client "[scope]"
CLI tool argument that generates boilerplate code for setting up the Refit client for a App or API running on Azure, like the examples I posted previously
static IServiceCollection ConfigureApiClient(this IServiceCollection services)
{
services.AddTransient<AzureAuthenticationHandler >(); // Only generate this if '--azure-client' was specified
services
.AddRefitClient<IApiClient>()
.ConfigureHttpClient(c => c.BaseAddress = "[base url value]")
.AddHttpMessageHandler<AzureAuthenticationHandler >() // Only generate this if '--azure-client' was specified
return services;
}
The message handler should only be generated if --azure-client
was specified
class AzureAuthenticationHandler : DelegatingHandler
{
private readonly TokenRequestContext context;
public ApiAuthenticationHandler()
{
context = new TokenRequestContext(new[] { "[OAuth scope defined from CLI argument]" });
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Headers.Authorization = await GetTokenAsync(cancellationToken);
var response = await base.SendAsync(request, cancellationToken);
return response;
}
private async Task<AuthenticationHeaderValue?> GetTokenAsync(CancellationToken cancellationToken)
{
var credentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions());
var token = await credentials.GetTokenAsync(context, cancellationToken);
return new AuthenticationHeaderValue("Bearer", token.Token);
}
}
If you haven't already started on something like this, then I can do it myself. But if you already have something similar in the works then let's stick with yours
from refitter.
Not started yet, still thinking on what I want and how sane it is, also less free time to work on this as I had hoped.
Take your time @Roflincopter. Time is precious and it is the only non-renewable resource we humans have. I just think that its awesome that there is life in this little thing I started
I was rather thinking of using the refitter
[Headers("Authorization: x")]
mechanism. The way I was thinking of implementing it was as a commandline option for refitter:--usesecurityscheme "jwt-auth"
where "jwt-auth" is one of the security schemes available in the openapi specification you are generating for.
Do you mind if we use -
as a word separator in CLI arguments? I just think its more readable to see --use-security-scheme
than --usesecurityscheme
. If you have a better reason then I will welcome it. I have strong opinions but they are weakly held
Generally you will only use one type of authentication in your project anyways and generally all the authorized calls will support all the of the possible authentication methods. That is an assumption on my side but I think it's a sane assumption.
It's normal for clients to use a single authentication method, but its also normal for servers to offer multiple authentication methods, all depending on what sorts of clients are communicating with it. OAuth bearer tokens are nice since you can have claims, roles, scopes, and complex sets of customized claims, to describe what resources the client can access based on the clients identity. OAuth bearer tokens on the other hand requires a secure token service that owns the claims used by the server and grants access to the client. Client Certificates are secure but there is no way elegant way to define to which resources the client can access, at least nothing as elegant as OAuth scopes. Basic Authentication rarely makes sense, except for web hooks where the client has no real ties to the server but was configured to call endpoints on it, for basic authentication it will also make sense to have security on the message level, like HMAC signatures, and have the server to just ignore unknown/invalid/unsigned messages
For each method that supports the given security-scheme, I will generate a method with the method level attribute:
[Headers("Authorization: x")]
where x is the right type for that security scheme, either bearer or basic, etc.Each method that has no security schemes will not have the attribute,
I like this 👍
Each method that has other security schemes but not the one you chose will not be generated? (Will this ever happen?)
I can't see a scenario where the same client uses multiple security schemes, but it is common practice to have multiple security schemes to cater to different types of clients. I've built quite a few API's that both have OAuth 2.0 Bearer Tokens and Client Certificates that are used separately by different client systems
You can configure the refit library to register a callback for authorization header input. So you can do this without attaching a generic header manipulator/adder class to the http client. But rather some lightweight lambda.
I had no idea that this was possible!
from refitter.
@jbt00000 The idea died out a bit and I think its because most users figured out that they can configure authorization through HttpClient delegating handlers that you can configure Refit to use using the Refit.HttpClientFactory tooling
I usually have something like this configured:
static IServiceCollection ConfigureApiClient(this IServiceCollection services)
{
services.AddTransient<AzureAuthenticationHandler >(); // Only generate this if '--azure-client' was specified
services
.AddRefitClient<IApiClient>()
.ConfigureHttpClient(c => c.BaseAddress = "[base url value]")
.AddHttpMessageHandler<AzureAuthenticationHandler >() // Only generate this if '--azure-client' was specified
return services;
}
If you're using .NET Core then Refitter can generate IServiceCollection
extension methods by using a .refitter
settings file. So by having a settings file like this:
{
"openApiPath": "./OpenAPI/v3.0/petstore.json",
"namespace": "Petstore",
"outputFolder": "GeneratedCode",
"outputFilename": "SwaggerPetstoreDirect.cs",
"naming": {
"useOpenApiTitle": false,
"interfaceName": "SwaggerPetstoreDirect"
},
"dependencyInjectionSettings": {
"baseUrl": "https://petstore3.swagger.io/api/v3",
"usePolly": true,
"pollyMaxRetryCount": 3,
"firstBackoffRetryInSeconds": 0.5,
"httpMessageHandlers": [
"AzureAuthenticationHandler"
]
},
"codeGeneratorSettings": {
"dateType": "System.DateTime",
"dateTimeType": "System.DateTime",
"arrayType": "System.Collections.Generic.IList"
},
"operationNameGenerator": "default",
"typeAccessibility": "internal"
}
and using Refitter with the --settings-file
argument like this:
refitter --settings-file petstore.refitter
The generated code will contain this extension method:
namespace Petstore
{
using System;
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Contrib.WaitAndRetry;
using Polly.Extensions.Http;
public static partial class IServiceCollectionExtensions
{
public static IServiceCollection ConfigureRefitClients(this IServiceCollection services, Action<IHttpClientBuilder>? builder = default)
{
var clientBuilderISwaggerPetstoreDirect = services
.AddRefitClient<ISwaggerPetstoreDirect>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://petstore3.swagger.io/api/v3"))
.AddHttpMessageHandler<AzureAuthenticationHandler>()
.AddPolicyHandler(
HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
Backoff.DecorrelatedJitterBackoffV2(
TimeSpan.FromSeconds(0.5),
3)));
builder?.Invoke(clientBuilderISwaggerPetstoreDirect);
return services;
}
}
}
hope this helps @jbt00000
from refitter.
Related Issues (20)
- IAsyncEnumerable - JSONL/application/x-ndjson HOT 7
- Hyphens with JsonStringEnumConverter results in `JSON value could not be converted` HOT 7
- Response is always nullable under `generateNullableReferenceTypes` HOT 2
- Source generator does not work in .Net 8 HOT 1
- Support for OpenAPI version 3.1.0 HOT 2
- Non string/boolean CodeGeneratorSettings are not honored HOT 3
- Generate the client from multiple versions HOT 1
- `remove-unused-schema` not working for collection-type responses HOT 1
- Asana API "cannot derive from sealed type" HOT 4
- Code Generator creates unsafe interface method names HOT 1
- Code Generator adds numeric suffix to Interface method name when not needed HOT 3
- Add the facility to exclude namespaces from generated code HOT 5
- Asana API specs strange naming HOT 2
- Refit Fails on Jira OpenAPI specs HOT 5
- Serializer improvements HOT 1
- All Contributors Bot HOT 12
- Polly.Extensions.Http deprecated in favour of Microsoft.Extensions.Http.Resilience HOT 3
- Support generating immutable records HOT 8
- AnyType + BodySerializationMethod HOT 4
- Support generating multiple files HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from refitter.