I am very, very new to the entire idea of REST and calling an API from http in general, but for a project I am working on, it has become necessary.
I am using ASP.NET Core, so I've been trying to find a REST library. When I was using standard .NET, I could use RestSharp, but the community made RestSharp.Core is pretty out of date and has many incompatibilities with newer versions of .NET Standard 1.6+.
To that end, I've explored other options, but my inexperience just makes it frustrating. Inevitably, I'm thinking it is best if I just use the built in HttpClient class. But I'm not sure how to do that for this exact scenario. I'm having a very hard time understanding how to give the parameters to the request, and what I'm specifically looking for in the return value.
My needs are pretty simple;
create a connection to $url
specify that it is a POST operation.
pass an existing JSON object to the server when making the request.
get JSON data back.
My code, using old RestSharp.Core, looks a bit like this - obviously keys and such omitted for privacy.
public async Task<string> OpenApiAsync() {
var token = await Task.Run(async () => {
var httpClient = new RestClient("https://[OMITTED].auth0.com/oauth/token");
var httpRequest = new RestRequest() {
Serializer = new RestSharp.Serializers.JsonSerializer(),
Method = Method.POST
};
httpRequest.AddHeader("content-type", "application/json");
httpRequest.AddJsonBody(new {
client_id = _settings.Value.ClientId,
client_secret = _settings.Value.ClientSecret,
audience = _settings.Value.Audience,
grant_type = _settings.Value.GrantType
});
var httpResponse = await httpClient.Execute(httpRequest);
var deserializer = new RestSharp.Deserializers.JsonDeserializer();
return deserializer.Deserialize<Dictionary<string, string>>(httpResponse);
});
return token["access_token"]);
}
The _settings object is injected with IOptions<Auth0Settings>, which has this shape and general data.
"authentication": {
"Domain": "[auth0-domain]",
"Audience": "https://[OMITTED].auth0.com/api/v2/",
"ClientId": "ABCDEFGHIJKLMNOP....",
"ClientSecret": "A22u5hgbnwifhwihfwi20u559f...",
"CallbackUrl": "http://localhost:5000/signin-auth0",
"GrantType": "client_credentials"
}
Can anyone help me understand how this could be ported to the native HttpClient that is in .NET Standard 1.6+? I specifically need one that is compatible with netstandard1.6 and netcoreapp1.1.
HttpClient is a very good http client and should be used in .NET core moving forward :)
private static async Task<string> OpenApiAsync()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("content-type", "application/json");
var body = JsonConvert.SerializeObject(YOUR_BODY);
var result = await client.PostAsync("https://[OMITTED].auth0.com/oauth/token", new StringContent(body , Encoding.UTF8));
var deserialized = JsonConvert.DeserializeObject<Dictionary<string, string>>(await result.Content.ReadAsStringAsync());
return deserialized["access_token"];
}
Related
I've searched for examples but couldn't find out exactly how the Azure SDK for .NET works.
Project is a net5.0 ASP.NET MVC.
There's already a question Mapping between Azure REST API and .NET SDK but it doesn't answer anything. There are good examples here: https://csharp.hotexamples.com/examples/Microsoft.Azure.Subscriptions/SubscriptionClient/-/php-subscriptionclient-class-examples.html but none of them seem to work for me fully (see next lines).
Microsoft.Azure.Management.ResourceManager.Models.Subscription has half of the fields get only, without set.
Recommended Azure.ResourceManager.Resources.Models.Subscription has everything get only.
I cannot create objects with these namespaces.
[AuthorizeForScopes(Scopes = new string[] { "https://management.azure.com/user_impersonation" })]
private async Task<Microsoft.Azure.Management.ResourceManager.Models.Subscription> GetUserSubscriptions()
{
var accessToken = Request.Headers[HeaderNames.Authorization];
Microsoft.Azure.Management.ResourceManager.Models.Subscription sub = new Microsoft.Azure.Management.ResourceManager.Models.Subscription();
using (var httpClient = new HttpClient())
{
string token = await _tokenAcquisition.GetAccessTokenForUserAsync(GraphConstants.ManagementScopes);
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
using (var response = await httpClient.GetAsync("https://management.azure.com/subscriptions?api-version=2020-01-01"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
sub = JsonConvert.DeserializeObject<Microsoft.Azure.Management.ResourceManager.Models.Subscription>(apiResponse);
}
}
return sub;
}
The variable apiResponse correctly retrieves the JSON information, but JsonConvert doesn't convert correctly.
Question:
How can I use Azure SDK (or any other) to map the API responses to objects that I can then manipulate.
I am new to Amazon Web Services.
I configured domain to use ElasticSearch in AWS(Amazon Web Services) console. Confirured usage of Http Requests.
Went through documantation of creating ElasticSearch client from
https://www.elastic.co/guide/en/elasticsearch/client/net-api/1.x/security.html
var response = client.RootNodeInfo(c => c
.RequestConfiguration(rc => rc
.BasicAuthentication("UserName", "Password")
));
Works fine to me (Response is 200)
But when i try to configure authentication credentials like this and pass config to client constructor i need to have "cloudId" i didnt find in at AWS where sould i search for it? or what i have to do?
My client code:
BasicAuthenticationCredentials credentials = new BasicAuthenticationCredentials("UserName", "Password");
var config = new ConnectionSettings("cloudId???", credentials);
var client = new ElasticClient(config);
var response = client.Ping();
I recently did this but a different way. I used the Nuget package AwsSignatureVersion4 and an IAM user with appropriate permissions to the ElasticSearch service.
But basically, use the ImmutableCredentials and just do what I need to do via the REST calls and the C# HttpClient. I find it easier than using the .NET ElasticSearch library. I can then copy/paste back and forth from Kibana.
var credentials = new ImmutableCredentials("access_key", "secret_key", null);
HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(someObjOrQuery), Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var resp = httpClient.PostAsync(es_url,
httpContent,
regionName: "us-east-1",
serviceName: "es",
credentials: credentials).GetAwaiter().GetResult();
if(resp.IsSuccessStatusCode)
{
//Good to go
}
else
{
//this gets what ES sent back
var content = response.Content.ReadAsStringAsync();
dynamic respJson = JObject.Parse(content.Result());
//Now you can access stuff by dot and it's dynamic respJson.something
}
I am setting up a PHP API and a web-page based on client-side Blazor. But for some reason CORS is triggered and my login process or any requests to my PHP pages result in CORS errors.
I started out testing my PHP API with a C# console app and the Blazor app, I tried using without any database access to test the functionality. The Blazor is right now running with Preview 9. The PHP version is 5.3.8. I could in theory update it, but several other active projects are running on it and I do not have any test environment. MySQL version 5.5.24.
First I figured it might have been because I was running it on my local machine, so I pushed it to the website where the PHP and MySQL is also running. Still I run into this CORS error.
I am still just testing this, so I have tried setting it to allow any origin. I have not had any experience with CORS before this. Pretty sure I ought to be able to add PHP code in each file I access that should allow CORS, but since it should all be on the same website, I figure CORS should not even be relevant?
PHP Code:
function cors() {
// Allow from any origin
if (isset($_SERVER['HTTP_ORIGIN'])) {
// Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
// you want to allow, and if so:
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
}
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
// may also be using PUT, PATCH, HEAD etc
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
echo "You have CORS!";
}
cors();
C# code using the injected HttpClient:
var resp = await Http.GetStringAsync(link);
The error I get is:
Access to fetch at 'https://titsam.dk/ntbusit/busitapi/requestLoginToken.php' from origin 'https://www.titsam.dk' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The response I hoped to get was that the link I use return a token for the login as it does for my API.
Is it because its running client side maybe and this triggers CORS? But that does not seem to explain why I cannot make it allow all.
Update:
My C# code in OnInitializedAsync:
link = API_RequestLoginTokenEndPoint;
Http.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "basic:testuser:testpass");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, link);
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = "include"
};
var response = await Http.SendAsync(requestMessage);
var responseStatusCode = response.StatusCode;
var responseBody = await response.Content.ReadAsStringAsync();
output = responseBody + " " + responseStatusCode;
Update 2:
It finally works. The C# code I linked is the solution Agua From Mars suggested and it solved the problem to use SendAsync with a HttpRequestMessage and adding the Fetch property include credentials to it. Another alternative was to add this line to the startup:
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
Then I could keep doing what I did to begin with, using GetStringAsync as it becomes the default.
await Http.GetStringAsync(API_RequestLoginTokenEndPoint);
So all the solutions Agua From Mars suggested worked. But I encountered a browser problem, where it kept the CORS issue in the cache somehow even after it had gotten solved, so it seemed like nothing had changed. Some code changes would show a different result, but I guess the CORS part was kept alive. With Chrome it helped opening a new pane or window. In my Opera browser this was not enough, I had to close all panes with the site open to ensure it would clear the cache and then opening a new window or pane with the site works in Opera as well. I had already in both browsers trying to use ctrl-F5 and Shift-F5 to get them to clear the cache. This did not change anything.
I hope this will help others avoid spending 2-3 days on an issue like this.
update 3.1-preview3
In 3.1-preview3, we cannot use the fetch option per message, the options is global
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
WebAssemblyHttpMessageHandler has been removed. The HttpMessageHanlder used is WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler from WebAssembly.Net.Http but don't include WebAssembly.Net.Http in your depencies or the application will failled to launch.
If you want to use the HttpClientFactory you can implement like that :
public class CustomDelegationHandler : DelegatingHandler
{
private readonly IUserStore _userStore;
private readonly HttpMessageHandler _innerHanler;
private readonly MethodInfo _method;
public CustomDelegationHandler(IUserStore userStore, HttpMessageHandler innerHanler)
{
_userStore = userStore ?? throw new ArgumentNullException(nameof(userStore));
_innerHanler = innerHanler ?? throw new ArgumentNullException(nameof(innerHanler));
var type = innerHanler.GetType();
_method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue(_userStore.AuthenticationScheme, _userStore.AccessToken);
return _method.Invoke(_innerHanler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(p =>
{
var wasmHttpMessageHandlerType = Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
})
.AddTransient<CustomDelegationHandler>()
.AddHttpClient("MyApiHttpClientName")
.AddHttpMessageHandler<CustonDelegationHandler>();
}
3.0 -> 3.1-preview2
On Blazor client side your need to tell to the Fetch API to send credentials (cookies and authorization header).
It's describe in the Blazor doc Cross-origin resource sharing (CORS)
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
ex:
#using System.Net.Http
#using System.Net.Http.Headers
#inject HttpClient Http
#code {
private async Task PostRequest()
{
Http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "{OAUTH TOKEN}");
var requestMessage = new HttpRequestMessage()
{
Method = new HttpMethod("POST"),
RequestUri = new Uri("https://localhost:10000/api/TodoItems"),
Content =
new StringContent(
#"{""name"":""A New Todo Item"",""isComplete"":false}")
};
requestMessage.Content.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue(
"application/json");
requestMessage.Content.Headers.TryAddWithoutValidation(
"x-custom-header", "value");
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
var response = await Http.SendAsync(requestMessage);
var responseStatusCode = response.StatusCode;
var responseBody = await response.Content.ReadAsStringAsync();
}
}
You can set up this option globaly with WebAssemblyHttpMessageHandlerOptions.DefaultCredentials static proprerty.
Or you can implement a DelegatingHandler and set it up in DI with the HttpClientFactory:
public class CustomWebAssemblyHttpMessageHandler : WebAssemblyHttpMessageHandler
{
internal new Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken);
}
}
public class CustomDelegationHandler : DelegatingHandler
{
private readonly CustomWebAssemblyHttpMessageHandler _innerHandler;
public CustomDelegationHandler(CustomWebAssemblyHttpMessageHandler innerHandler)
{
_innerHandler = innerHandler ?? throw new ArgumentNullException(nameof(innerHandler));
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = "include"
};
return _innerHandler.SendAsync(request, cancellationToken);
}
}
In Setup.ConfigureServices
services.AddTransient<CustomWebAssemblyHttpMessageHandler>()
.AddTransient<WebAssemblyHttpMessageHandler>()
.AddTransient<CustomDelegationHandler>()
.AddHttpClient(httpClientName)
.AddHttpMessageHandler<CustomDelegationHandler>();
Then you can create an HttpClient for your API with IHttpClientFactory.CreateClient(httpClientName)
To use the IHttpClientFactory you need to install Microsoft.Extensions.Http package.
3.0-preview3 => 3.0-preview9
Replace WebAssemblyHttpMessageHandler with BlazorHttpMessageHandler
I'm developing an ASP.Net Core web application where I need to create a kind of "authentication proxy" to another (external) web service.
What I mean by authentication proxy is that I will receive requests through a specific path of my web app and will have to check the headers of those requests for an authentication token that I'll have issued earlier, and then redirect all the requests with the same request string / content to an external web API which my app will authenticate with through HTTP Basic auth.
Here's the whole process in pseudo-code
Client requests a token by making a POST to a unique URL that I sent him earlier
My app sends him a unique token in response to this POST
Client makes a GET request to a specific URL of my app, say /extapi and adds the auth-token in the HTTP header
My app gets the request, checks that the auth-token is present and valid
My app does the same request to the external web API and authenticates the request using BASIC authentication
My app receives the result from the request and sends it back to the client
Here's what I have for now. It seems to be working fine, but I'm wondering if it's really the way this should be done or if there isn't a more elegant or better solution to this? Could that solution create issues in the long run for scaling the application?
[HttpGet]
public async Task GetStatement()
{
//TODO check for token presence and reject if issue
var queryString = Request.QueryString;
var response = await _httpClient.GetAsync(queryString.Value);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
[HttpPost]
public async Task PostStatement()
{
using (var streamContent = new StreamContent(Request.Body))
{
//TODO check for token presence and reject if issue
var response = await _httpClient.PostAsync(string.Empty, streamContent);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType?.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
}
_httpClient being a HttpClient class instantiated somewhere else and being a singleton and with a BaseAddressof http://someexternalapp.com/api/
Also, is there a simpler approach for the token creation / token check than doing it manually?
If anyone is interested, I took the Microsoft.AspNetCore.Proxy code and made it a little better with middleware.
Check it out here: https://github.com/twitchax/AspNetCore.Proxy. NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft archived the other one mentioned in this post, and I plan on responding to any issues on this project.
Basically, it makes reverse proxying another web server a lot easier by allowing you to use attributes on methods that take a route with args and compute the proxied address.
[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}
I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.
It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.
This post talks about writing a simple HTTP proxy logic in C# or ASP.NET Core. And allowing your project to proxy the request to any other URL. It is not about deploying a proxy server for your ASP.NET Core project.
Add the following code anywhere of your project.
public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
This method covert user sends HttpContext.Request to a reusable HttpRequestMessage. So you can send this message to the target server.
After your target server response, you need to copy the responded HttpResponseMessage to the HttpContext.Response so the user's browser just gets it.
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}
var response = context.Response;
response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}
And now the preparation is complete. Back to our controller:
private readonly HttpClient _client;
public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}
public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return new EmptyResult();
}
And try to access it. It will be proxied to google.com
A nice reverse proxy middleware implementation can also be found here: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/
Note that I replaced this line here
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
with
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Original headers (e.g. like an authorization header with a bearer token) would not be added without my modification in my case.
I had luck using twitchax's AspNetCore.Proxy NuGet package, but could not get it to work using the ProxyRoute method shown in twitchax's answer. (Could have easily been a mistake on my end.)
Instead I defined the mapping in Statup.cs Configure() method similar to the code below.
app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
string url = "https://someexternalapp.com/" + args["arg1"];
return await Task.FromResult<string>(url);
});
Piggy-backing on James Lawruk's answer https://stackoverflow.com/a/54149906/6596451 to get the twitchax Proxy attribute to work, I was also getting a 404 error until I specified the full route in the ProxyRoute attribute. I had my static route in a separate controller and the relative path from Controller's route was not working.
This worked:
public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
This does not:
[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
Hope this helps someone!
Twitchax's answer seems to be the best solution at the moment. In researching this, I found that Microsoft is developing a more robust solution that fits the exact problem the OP was trying to solve.
Repo: https://github.com/microsoft/reverse-proxy
Article for Preview 1 (they actually just released prev 2): https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/
From the Article...
YARP is a project to create a reverse proxy server. It started when we noticed a pattern of questions from internal teams at Microsoft who were either building a reverse proxy for their service or had been asking about APIs and technology for building one, so we decided to get them all together to work on a common solution, which has become YARP.
YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET. The key differentiator for YARP is that it is being designed to be easily customized and tweaked to match the specific needs of each deployment scenario. YARP plugs into the ASP.NET pipeline for handling incoming requests, and then has its own sub-pipeline for performing the steps to proxy the requests to backend servers. Customers can add additional modules, or replace stock modules as needed.
...
YARP works with either .NET Core 3.1 or .NET 5 preview 4 (or later). Download the preview 4 (or greater) of .NET 5 SDK from https://dotnet.microsoft.com/download/dotnet/5.0
More specifically, one of their sample apps implements authentication (as for the OP's original intent)
https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs
Here is a basic implementation of Proxy library for ASP.NET Core:
This does not implement the authorization but could be useful to someone looking for a simple reverse proxy with ASP.NET Core. We only use this for development stages.
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Sample.Proxy
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(options =>
{
options.AddDebug();
options.AddConsole(console =>
{
console.IncludeScopes = true;
});
});
services.AddProxy(options =>
{
options.MessageHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = true
};
options.PrepareRequest = (originalRequest, message) =>
{
var host = GetHeaderValue(originalRequest, "X-Forwarded-Host") ?? originalRequest.Host.Host;
var port = GetHeaderValue(originalRequest, "X-Forwarded-Port") ?? originalRequest.Host.Port.Value.ToString(CultureInfo.InvariantCulture);
var prefix = GetHeaderValue(originalRequest, "X-Forwarded-Prefix") ?? originalRequest.PathBase;
message.Headers.Add("X-Forwarded-Host", host);
if (!string.IsNullOrWhiteSpace(port)) message.Headers.Add("X-Forwarded-Port", port);
if (!string.IsNullOrWhiteSpace(prefix)) message.Headers.Add("X-Forwarded-Prefix", prefix);
return Task.FromResult(0);
};
});
}
private static string GetHeaderValue(HttpRequest request, string headerName)
{
return request.Headers.TryGetValue(headerName, out StringValues list) ? list.FirstOrDefault() : null;
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets()
.Map("/api", api => api.RunProxy(new Uri("http://localhost:8833")))
.Map("/image", api => api.RunProxy(new Uri("http://localhost:8844")))
.Map("/admin", api => api.RunProxy(new Uri("http://localhost:8822")))
.RunProxy(new Uri("http://localhost:8811"));
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
I'm trying to implement Onedrive client login by using Connect to identity providers with Web Account Manager
With this method finally I get a token using this code
private static async Task<string> RequestTokenAndSaveAccount(WebAccountProvider Provider, String Scope, String ClientID)
{
try
{
WebTokenRequest webTokenRequest = new WebTokenRequest(Provider, "wl.signin onedrive.appfolder onedrive.readwrite", ClientID);
WebTokenRequestResult webTokenRequestResult = await WebAuthenticationCoreManager.RequestTokenAsync(webTokenRequest);
if (webTokenRequestResult.ResponseStatus == WebTokenRequestStatus.Success)
{
App.settings.onedriveStoredAccountKey = webTokenRequestResult.ResponseData[0].WebAccount.Id;
return webTokenRequestResult.ResponseData[0].Token;
}
return "";
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return "";
}
}
But I can't use the returned token to create a OnedriveClient because I need a MsaAuthenticationProvider to create the client and it creates its own token ignoring the one coming from the WebTokenRequest, and it doesn't have any method to take the prior token.
There is a way to create a OneDriveClient without going to REST Onedrive API?
Thank you
Edit:
As there are (at this time) two main versions of OneDriveSDK and those are different from each other, there are two ways to achieve this.
OneDrive.SDK 1.x
As #Brad said, an IAuthenticationProvider is needed to create the OneDriveClient.
I got the solution from https://github.com/ginach/Simple-IAuthenticationProvider-sample-for-OneDrive-SDK.
I took the SimpleAuthenticationProvider into my code, and then created the client like this
var client = new OneDriveClient(
new AppConfig(),
/* credentialCache */ null,
new Microsoft.OneDrive.Sdk.HttpProvider(),
new ServiceInfoProvider(new SimpleAuthenticationProvider { CurrentAccountSession = new Microsoft.OneDrive.Sdk.AccountSession { accessToken = AccessToken } }),
ClientType.Consumer);
client.BaseUrl = "https://api.onedrive.com/v1.0";
await client.AuthenticateAsync();
Where the accessToken is taken from the RequestTokenAndSaveAccount method.
OneDrive.SDK 2.x
For this case, the answer given by #dabox is the right solution.
Appending to Brad's answer, you can create a new AuthenticationProivder implements the IAuthenticationProivder interface in the package Microsoft.Graph.Core. And there also is a DelegateAuthenticationProvider in package Microsoft.Graph.Core which provides a Delegate interface for you. An example looks like:
OneDriveClient oneDriveClient = new OneDriveClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await GetAccessTokenSomeWhereAsync();
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}));
return oneDriveClient ;
Modified based on the Microsoft Graph's asp .net example: https://github.com/microsoftgraph/aspnet-connect-sample/blob/master/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Helpers/SDKHelper.cs#L18
OneDriveClient only requires an IAuthenticationProvider, which is a pretty simplistic interface. You can create your own and implement AuthenticateRequestAsync such that it calls your RequestTokenAndSaveAccount and then adds the bearer token to the request.