I am using httpClientFactory to request a Wechat Pay API, but the Wechat Pay API needs a certificate. How can I configure httpClientFactory to use the certificate?
If you look at the Default factory returned in DI, it inherits from two interfaces, and when injecting you chose to only use one:
internal class DefaultHttpClientFactory : IHttpClientFactory, IHttpMessageHandlerFactory
Knowing this you can take a sneaky workaround:
_handlerFactory = _clientFactory as IHttpMessageHandlerFactory;
Now if you look at the handler factory, it has a method CreateHandler with the same string argument as the CreateClient method. Actually if you look at the implementation of CreateClient, you can see it uses this method which then caches the handler.
So forget about Startup: Cast your client factory to handler factory, create a handler for your specific user, verify all properties on the handler (like is the certificate there or do you need to add it since it's a new instance) and then call CreateClient as usual.
Edit: In startup you may still need to add ConfigurePrimaryHttpMessageHandler to actually create a HttpClientHandler, so you may have to make a consession of only registering one named HttpClient and just swapping the certificate. It still gives you the advantages of having the factory though.
Edit 2: know that the handler is wrapped a bunch of times in decorators, so you need to dig deep. The trick I use is:
var handler = _handlerFactory.CreateHandler("customer");
while (handler as DelegatingHandler != null)
{
handler = (handler as DelegatingHandler).InnerHandler;
}
var clientHandler = handler as HttpClientHandler;
First, you need register you HttpClient:
services.AddHttpClient("signed")
.ConfigurePrimaryMessageHandler(() =>
{
var handler = new HttpClientHandler();
var certificate = new X509Certificate2(filename: "foo.pfx", password: "123");
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(certificate);
});
In the example the file foo.pfx contains a certificate with the password 123.
signed is the name of HttpClient.
Second, you'll call IHttpClientFactory.CreateClient to create the HttpClient instance.
public class PayClient
{
private readonly IHttpClientFactory _httpClientFactory;
public PayClient(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task SomePayMethodAsync()
{
using (httpClient = _httpClientFactory.CreateClient("signed"))
{
// use httpClient
}
}
}
You need use same name signed as the parameter of CreateClient.
LATER UPDATE
If you write SAAS with many certificates you may create and configure an HttpClient manually every time you need it. It's the simplest way.
public class PayClient
{
private readonly ICurrentUserProvider _currentUserProvider;
public PayClient(ICurrentUserProvider _currentUserProvider)
{
_currentUserProvider = currentUserProvider;
}
private HttpClient CreateHttpClient()
{
var currentUser = _currentUserProvider.CurrentUser;
var filename = currentUser.CertificateFilename;
var password = currentUser.CertificatePassword;
var handler = new HttpClientHandler();
var certificate = new X509Certificate2(filename, password);
handler.ClientCertificateOptions = ClientCertificateOptions.Manual;
handler.ClientCertificates.Add(certificat);
return new HttpClient(handler);
}
public async Task SomePayMethodAsync()
{
using (httpClient = CreateHttpClient())
{
// use httpClient
}
}
}
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new SocketsHttpHandler()
{
UseCookies = false,
};
});
Related
I am following one article about Blazor WebAssembly Authentication.
https://code-maze.com/blazor-webassembly-authentication-aspnetcore-identity/
This is AuthenticationService.cs.
public async Task<AuthResponseDto> Login(UserForAuthenticationDto userForAuthentication)
{
var content = JsonSerializer.Serialize(userForAuthentication);
var bodyContent = new StringContent(content, Encoding.UTF8, "application/json");
var authResult = await _client.PostAsync("accounts/login", bodyContent);
var authContent = await authResult.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<AuthResponseDto>(authContent, _options);
if (!authResult.IsSuccessStatusCode)
return result;
await _localStorage.SetItemAsync("authToken", result.Token);
((AuthStateProvider)_authStateProvider).NotifyUserAuthentication(userForAuthentication.Email);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.Token);
return new AuthResponseDto { IsAuthSuccessful = true };
}
public async Task Logout()
{
await _localStorage.RemoveItemAsync("authToken");
((AuthStateProvider)_authStateProvider).NotifyUserLogout();
_client.DefaultRequestHeaders.Authorization = null;
}
I lost my way in this part.
((AuthStateProvider)_authStateProvider).NotifyUserAuthentication(userForAuthentication.Email);
I can't get this code. Type casting? Type converting?
This code calls a method, NotifyUserAuthentication. But what is the front part's meaning?
Generally, I know ( ) in front of the variable is for casting.
But I don't get this what is this for, and what is this code meaning?
And why double used the same class AuthenticationStateProvider.
AuthStateProvider is inherited from AuthenticationStateProvider.
_authStateProvider is an instance of AuthenticationStateProvider.
Any help could be helpful for me.
Your AuthStateProvider DI service is registered as a AuthenticationStateProvider object, so in AuthenticationService that's what gets injected
private readonly AuthenticationStateProvider _authStateProvider;
public AuthenticationService(HttpClient client, AuthenticationStateProvider authStateProvider, ILocalStorageService localStorage)
NotifyUserLogout is an AuthStateProvider method, it's not implemented in it's parent AuthenticationStateProvider so you need to cast the object instance (which is actually a AuthStateProvider instance) to AuthStateProvider to be able to call the method.
Put in a break point and see what you have.
I'm refactoring my code to use Refit for my calls to the WebApi service. The interface is set up and I also created a delegating handler:
public class AuthHandler : DelegatingHandler
{
private readonly TokenProvider tokenProvider;
private readonly ISessionStorageService sessionStorage;
public AuthHandler (
TokenProvider tokenProvider,
ISessionStorageService sessionStorage)
{
this.tokenProvider = tokenProvider ?? throw new ArgumentNullException(nameof(tokenProvider));
this.sessionStorage = sessionStorage ?? throw new ArgumentNullException(nameof(sessionStorage));
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
{
var someToken = await sessionStorage.GetItemAsync<string>("sometoken");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenProvider.AccessToken);
request.Headers.Add("someToken", someToken);
return await base.SendAsync(request, ct).ConfigureAwait(false);
}
}
And in Startup.cs:
services.AddBlazoredSessionStorage();
services.AddScoped<TokenProvider>();
services.AddScoped<AuthHandler>();
services.AddRefitClient<IApiService>().ConfigureHttpClient(options =>
{
options.BaseAddress = new Uri(Configuration["Server:Url"]);
options.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}).AddHttpMessageHandler<AuthHandler>();
I have a razor component and I want to use the service above so I injected the services and did:
#code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var list = await myService.GetListAsync();
}
}
}
Also, in _Host.cshtml:
<environment include="Staging,Production">
<component render-mode="ServerPrerendered" type="typeof(App)" param-InitialState="tokens" />
</environment>
<environment include="Development">
<component render-mode="Server" type="typeof(App)" param-InitialState="tokens" />
</environment>
However I get the following exception:
System.InvalidOperationException: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.
So, just to make sure if I have values or not I added the following before the call to the api:
var someToken = await sessionStorage.GetItemAsync<string>("sometoken");
var accessToken = tokenProvider.AccessToken;
And I DO have values in both variables.
So why can't I access the session storage from the delegating handler? And why is token provider instance instantiated but the properties all null (also in the handler)?
EDIT
I only need one place to keep my tokens. It doesn't matter if it's the token provider or the session storage, as long as it works in blazor pages/componenents and other services.
UPDATE 1
One can skip DI and create the service like this:
var service = RestService.For<IMyService>(new HttpClient(new AuthHandler(tokenProvider, sessionStorage))
{
BaseAddress = new Uri(myUrl)
}
);
This will work as expected. However, it would be much more better to use DI. The problem might either be in Blazor or in Refit.
I think I have found a working solution to the problem.
Instead of relying on the HttpClientFactory used by Refit, I created my own DI logic.
Previous:
Uri apiBaseUri = dataAccessLayerConfiguration.API.BaseUrl;
foreach (Type repositoryType in repositoryTypes)
{
services
.AddRefitClient(repositoryType)
.ConfigureHttpClient((httpClient) => httpClient.BaseAddress = apiBaseUri)
.AddHttpMessageHandler<RequestHeaderHandler>();
}
Current:
foreach (Type repositoryType in repositoryTypes)
{
services.AddTransient(
repositoryType,
(provider) =>
{
return
RepositoryFactory.Create(
repositoryType,
configuration,
provider.GetRequiredService<ITokenCacheProvider>(),
() => provider.GetRequiredService<ICurrentUser>());
});
}
The RepositoryFactory I use creates the HttpClients for each API interface:
return
RestService.For<TRepository>(
new HttpClient(
new RequestHeaderHandler(tokenCacheProvider, fetchCurrentUserDelegate)
{
InnerHandler = new HttpClientHandler()
},
true)
{
BaseAddress = configuration.DataAccessLayer.API.BaseUrl
});
I currently get the current user in the code of my layout in the OnParametersSet() method. I'm not completely satisfied with it yet, but for now it's enough for me. However, it is important not to inject the user object directly when creating HttpClient, but only a delegate (or a Func<>) which then resolves the user object if needed.
This way I was able to work around the scope issue of Refit/HttpClientFactory, but still continue to work with Dependency Injection. It may not be a one hundred percent solution for everyone, but it could be enough to possibly find the right direction for your own project.
In my current application, I am using Service Stack with JWT's for security. Security has been implemented and works perfectly. Trouble is, I would like to secure one route differently from the others. There is a document the logged in user retrieves, I want to make sure the document they are retrieving is theirs and not someone else's. It is very sensitive data. I would like to secure it differently because something like PostMan could be used with a valid token to retrieve any document, I want to prevent this. The users id is in the token, I would like to match it against the document that is being retrieved if possible. The current security is implemented like so:
public class AppHost: AppHostBase
{
public override void Configure(Funq.Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JsonWebTokenAuthProvider("myKey", "myAudience"),
}));
}
}
JsonWebTokenAuthProvider is a custom class where security was implemented, this all works perfectly. Here is the code:
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
// first validate the token, then get roles from session
string header = request.oauth_token;
// if no auth header, 401
if (string.IsNullOrEmpty(header))
{
throw HttpError.Unauthorized(MissingAuthHeader);
}
string[] headerData = header.Split(' ');
// if header is missing bearer portion, 401
if (!string.Equals(headerData[0], "BEARER", StringComparison.OrdinalIgnoreCase))
{
throw HttpError.Unauthorized(InvalidAuthHeader);
}
// swap - and _ with their Base64 string equivalents
string secret = SymmetricKey.Replace('-', '+').Replace('_', '/');
string token = headerData[1].Replace("\"", "");
// set current principal to the validated token principal
Thread.CurrentPrincipal = JsonWebToken.ValidateToken(token, secret, Audience, true, Issuer);
string lanId = GetLanID(Thread.CurrentPrincipal.Identity.Name);
string proxyAsLanId = request.Meta.ContainsKey(META_PROXYID) ? request.Meta[META_PROXYID] : null;
if (HttpContext.Current != null)
{
// set the current request's user the the decoded principal
HttpContext.Current.User = Thread.CurrentPrincipal;
}
// set the session's username to the logged in user
session.UserName = Thread.CurrentPrincipal.Identity.Name;
session.Roles = GetApplicableRoles(lanId, proxyAsLanId);
authService.Request.SetItem("lanID", lanId);
authService.Request.SetItem("proxyAsLanId", proxyAsLanId);
return OnAuthenticated(authService, session, null, null);
}
I looked up RequestFilterAttribute found here, but I do not think that is what I want. Ideally, if the check fails I would like to return a 401 (unauthorized) if possible.
What is the best way to do this?
If you just want to handle one route differently than you can just add the validation in your single Service, e.g:
public object Any(MyRequest dto)
{
var lanId = base.Request.GetItem("lanId");
if (!MyIsValid(lanId))
throw HttpError.Unauthorized("Custom Auth Validation failed");
}
You could do the same in a RequestFilter, e.g:
public class CustomAuthValidationAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object responseDto)
{
var lanId = req.GetItem("lanId");
if (!MyIsValid(lanId))
{
res.StatusCode = (int) HttpStatusCode.Unauthorized;
res.StatusDescription = "Custom Auth Validation failed";
res.EndRequest();
}
}
}
And apply it to a single Service:
[CustomAuthValidation]
public object Any(MyRequest dto)
{
//...
}
Or a collection of Services, e.g:
[CustomAuthValidation]
public class MyAuthServices : Service
{
public object Any(MyRequest1 dto)
{
//...
}
public object Any(MyRequest2 dto)
{
//...
}
}
I'm unit testing my API service and all is well using the MockRquestContext. The calls to this.GetSession() always returns an IAuthSession, but I have a custom AuthUserSession and as far as I can tell, there's no way to create an instance of my custom AuthUserSession and add it to the mock context. Is this possible?
var service = container.Resolve<AgencyCaseService>();
service.SetResolver(new BasicResolver(container));
var context = new MockRequestContext() { ResponseContentType = ContentType.Json };
//Something like this
MyCustomAuthSession session = new MyCustomAuthSession() { set some values}
context.AuthSession = session//this doesn't exist but it's the type of thing i need to do
service.RequestContext = context;
The Session isn't on the Request Context, it requires a mixture of ICacheClient, SessionFeature and HttpRequest cookies to create.
You can look at the implementation for the way to mock it inside a Service, which shows it first tries to resolve it in a Container:
private object userSession;
protected virtual TUserSession SessionAs<TUserSession>()
{
if (userSession == null)
{
userSession = TryResolve<TUserSession>(); //Easier to mock
if (userSession == null)
userSession = Cache.SessionAs<TUserSession>(Request, Response);
}
return (TUserSession)userSession;
}
So to mock it you could just do:
container.Register(new MyCustomAuthSession() { /* set some values*/ });
How do I do constructor injection when I'm manually initializing the class?
public class ApiKeyHandler : DelegatingHandler
{
private IApiService apiService;
public ApiKeyHandler(IApiService apiService)
{
this.apiService = apiService;
}
}
Initializing:
var apiKey = new ApiKeyHandler(/*inject here */);
How do I accomplish this? My bindings and everything is already setup.
You want to do something like this:
var apiKey = new ApiKeyHandler(kernel.Get<IApiService>());
However, why not inject the ApiKeyHandler itself?
var apiKey = kernel.Get<ApiKeyHandler>();
Here is an article about Ninject:
You basically want to set this up at the beginning of your code and have it available globally:
public IKernel kernel = new StandardKernel();
...
kernel.Bind<IApiService>()
.To<SomeConcreteApiService>();