We're using ASP.NET Core 2.2 to build a Web API project that effectively is a smart proxy for another API. We don't want to do anything special with access tokens though, and just forward them when calling the other API.
I want to avoid "sync over async" calls, especially in constructors and DI resolvers and factories, while still relying on the framework to handle retrieving access tokens from the current http request.
Here's a representative repro of our scenario:
public class TheOtherApi : ITheOtherApi
{
private readonly HttpClient _client;
public TheOtherApi(HttpClient client, IHttpContextAccessor httpContextAccessor)
{
_client = client;
// Yuck! Sync over async, inside a constructor!
var token = httpContextAccessor.HttpContext.GetTokenAsync("access_token").Result;
client.SetBearerToken(token);
}
public Task<string> ForwardSomethingCool(MyCommand command)
{
// E.g.
var response await _client.PostAsync(command.path, command.body);
return await response.Content.ReadAsStringAsync();
}
}
Registrations in Startup along these lines:
services.AddScoped<ITheOtherApi, TheOtherApi>();
services.AddHttpContextAccessor();
services.AddScoped(_=> new HttpClient { BaseAddress = new Uri("https://example.org/api") });
This perfectly demonstrates my problem: there's a .Result in there, that I want to get rid of entirely, without just moving it to some kind of factory function registered in Startup.
Searching for a synchronous way to get the access token, I went down into the source of GetTokenAsync(...) and see what other method I could use intead. I find that it in fact has all sorts of side-effects, for example it does AuthenticateAsync(...) before doing what the method name suggests ("getting a token" from the context).
I actually only want the logic from GetTokenValue(...) without the other bits, and hurray (!): it is not async.... but that method relies on AuthenticationProperties which I don't have readily available from the HttpContextAccessor?
My current workaround (or 'solution'?) is to do the work myself:
httpContextAccessor.HttpContext.Request.Headers
.TryGetValue("Authorization", out var authorizationHeader);
var bearerToken = authorizationHeader
.SingleOrDefault()
?.Replace("bearer ", "", StringComparison.InvariantCultureIgnoreCase);
if (string.IsNullOrWhiteSpace(bearerToken))
{
throw new ArgumentException(nameof(httpContextAccessor), "HttpContextAccessor resulted in no Access Token");
}
_client.SetBearerToken(token);
How could I synchronously get the access_token from a IHttpContextAccessor without writing my own entire headers/string manipulation helper function to extract it from the Headers?
Related
Trying to write some integration tests for the first time in .NET - specifically for HotChocolate.
I’ve got had tests working with the WebApplicationFactory, the final part left is trying to now mock the authentication. I tried to set it up based on Mock Authentication but I’m querying the data with IRequestExecutor and finding the de-facto way of setting it up doesn't actually fire:
[Fact]
public async Task GetJokes()
{
var query =
#"query Jokes {
jokes(jokeLength: SMALL) {
nodes {
id
body
}
}
}";
var request = QueryRequestBuilder.New().SetQuery(query).Create();
var executor = await _factory.Services.GetRequestExecutorAsync();
var result = await executor.ExecuteAsync(request);
(await result.ToJsonAsync()).MatchSnapshot();
}
When I debug this I never hit HandleAuthenticateAsync in my AuthHandler’s (setup from the Mock Authentication linked above), so the claims are never added - so looks like those only run for the httpClient requests - is there a way to configure the IRequestExecutor to be authenticated too?
You can add the ClaimsPrincipal directly as property to the QueryRequestBuilder.
var request = QueryRequestBuilder
.New()
.SetQuery(query)
.AddProperty(nameof(ClaimsPrincipal), new ClaimsPrincipal(new ClaimsIdentity("test")))
.Create();
We are using http client factory and delegating handler to invoke a partner api and process the response. AuthenticationDelegating handler as shown below has a responsibility to get the required token for partner api authentication and provide it part of the api request.
Question - Should i register AuthenticationDelegationHandler as Transient or Singleton if the token expiration is set to 24 hours?
startup.cs
services.AddTransient<AuthenticationDelegatingHandler>();
var apiSettings = Configuration.GetSection(nameof(APIParameters)).Get<APIParameters>();
var apRegistrationParameters = Configuration.GetSection(nameof(AppRegistrationParameters)).Get<AppRegistrationParameters>();
services.AddHttpClient("ApiSecuredClient", client =>
{
client.BaseAddress = new Uri(ApiSettings.BaseUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ApiSettings.MediaType));
})
.AddHttpMessageHandler<AuthenticationDelegatingHandler>().SetHandlerLifetime(TimeSpan.FromMinutes(apRegistrationParameters.LifetimeOfAuthenticationHandlerInMinutes));
httpclient factory
public virtual async Task<HttpResponseMessage> GetRequestAsync<T>(T req, Sales360APIParameters
_paramenters) where T : class, IApiBaseRequest
{
using (var client = _httpClientFactory.CreateClient("XXXClient"))
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(_paramenters.MediaType));
var json = JsonConvert.SerializeObject(req);
var data = new StringContent(json, Encoding.UTF8, _paramenters.MediaType);
data.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("version", _paramenters.Version));
HttpResponseMessage Res = await client.PostAsync(_paramenters.API, data);
return Res;
}
}
AuthenticationDelegatingHandler
public class AuthenticationDelegatingHandler : DelegatingHandler
{
private AppRegistrationParameters _appRegistrationInfo;
private DateTime _tokenExpirationLocalTime;
private ILogger<AuthenticationDelegatingHandler> _logger;
public AuthenticationDelegatingHandler(ILogger<AuthenticationDelegatingHandler> logger, IConfiguration config)
{
_appRegistrationInfo = config.GetSection(nameof(AppRegistrationParameters)).Get<AppRegistrationParameters>();
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
Task<HttpResponseMessage> response = null;
if (_tokenExpirationLocalTime != null && _tokenExpirationLocalTime > DateTime.Now.AddSeconds(_appRegistrationInfo.TimeoutRequestInSeconds))
{
response = ProcessRequest(request);
if (response.Result.StatusCode == HttpStatusCode.Unauthorized || response.Result.StatusCode == HttpStatusCode.Forbidden)
response = ProcessRequest(request, true);
}
else
{
response = ProcessRequest(request, true);
}
return response.Result;
}
catch (Exception ex)
{
_logger.LogError("{systemId} - Error Authenticating with App. {errorCode} for " + request.Content, Constants.SystemId, Constants.ErrorCode3100);
_logger.LogError(ex.Message);
throw;
}
}
Custom delegating handler must always be registered as transient dependencies. All the examples in the Microsoft documentation show that custom delegating handlers must be registered as transient dependencies.
This stackoverflow question and this article explain the reasons behind this.
To summarize: always register your custom delegating handlers with transient lifetime when you are using the ASP.NET core HTTP client factory.
Notice that this guideline has nothing to do with your business logic to handle both the access token and its lifetime. It is a side effect of the way the ASP.NET core HTTP client factory handles the DI scope which uses to resolve dependencies (read the linked article for an in depth explanation).
If you want to take some ideas about how to handle access tokens and their expiration by using a delegating handler, this project can offer you some insights.
The concept of token expired and HttpDelegationHandler is just quite un-relative.
One that should stick with HttpRequestMessage and the other should stick with HttpClient.
In short, you're trying to interact with the request, response in the exact time that the request was sent, which possibility can get an unauthorize response. That can be done right, but it's quite complicated.
Instead, why not just consider another approach ? Like a background service that run every 30 mins, if your token expire in 3 hour or less, take a new token and store it either on cache or database ? I think that just much more clean and easier than the way you're trying to solve the same problem.
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 have jwt auth:
var messageHandlers = new JwtMessageHandler(_serviceProvider);
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new JwtBearerEvents
{
OnMessageReceived = messageHandlers.OnMessageReceived,
},
TokenValidationParameters = tokenValidationParameters
});
The JwtMessageHandler is my custom handler. In the handler I have to make some queries to database, so I pass ServiceProvider and resolve my user service:
public class JwtMessageHandler
{
private IUserService _userService;
public async Task OnMessageReceived(MessageReceivedContext arg)
{
//parsing header, get claims from token
...
_userService = (IUserService)arg.HttpContext.RequestServices.GetService(typeof(IUserService));
var isRoleChanged = await _userService.IsRoleChanged(tokenObject.Subject, rolesFromToken);
if (isRoleChanged)
{
GenerateBadResponse(arg);
return;
}
var canLogin = await _userService.CanLogin(tokenObject.Subject);
if (!canLogin)
{
GenerateBadResponse(arg);
return;
}
}
}
In the service I make queries:
...
var user = await _userManager.FindByEmailAsync(email);
var currentRoles = await _userManager.GetRolesAsync(user);
..
The OnMessageReceived is called for every request.
When I have one request on page to the server or I wait one-two seconds before doing something all works fine. But, I have several pages where I make 2-3 simultaneous requests to the server. And, in this case I get error about:
The connection was not closed. The connection's current state is connecting
I understand that problem with multithreading. The JwtMessageHandler is created once when application is started. So, I put the line:
_userService = (IUserService)_serviceProvider.GetService(typeof(IUserService));
inside method, before it was located in the constructor. But, It didn't help. Also, I tried to set null to _userService in the end of my method.
How to correctly use in this case?
Trying to use a connection that is already "connecting" - clear sign of some race condition.
Re-check that IUserService is registered with "scope" lifetime, and all it dependencies (userManager, dbContext) too
Do not use IServiceProvider you obtained during app startup for scope-bases services resolution - it is NOT related to current request scope and return instances from "some other universe". Use HttpContext.RequestServices for service resolution.
Check that your are "awaiting" all async methods. If you start second request while still executing first one - you may possibly "catch" dbContext during "connecting" stage.
Your JwtMessageHandler instance is one/single per app. So don't use it's property for storing _userService (remove private IUserService _userService). Instead, use local variable inside OnMessageReceived (var _userService = ...).
You already checked (1), (2) and (3). I think (4) is the last one you need to fix your bug.
#Dmitry's answer pointed me in the right direction. For me, I was having this issue in a MiddleWare on .NETCORE. What solved this issue for me is resolving the IUnitOfWork interface in the Invoke method of my Middleware.
I did something like
public Task Invoke(HttpContext context)
{
_unitOfWork = (IUnitOfWork)context.RequestServices.GetService(typeof(IUnitOfWork));
//Some checks
var apiKey = context.Request.Headers["X-ApiKey"][0];
var clientApp = _unitOfWork.ClientApplicationsRepository.Items.FirstOrDefault(s => s.ApiKey == apiKey);
//Some other code...
return _next(context);
}
I have faced this situation a lot.
I have always gotten by by using lock keyword.
lock (_context)
{
var user = _context.users.first(x => x.Id == userId);
}
This locks the use of the object (i.e. _context) for the current thread and no other thread simultaneously can access this same instance hence no problems whatsoever.
In my case I was trying to use Managed Identity and constructed a SqlConnection in ConfigureServices and did AddDbContext<..>(o => o.UseSqlServer(sqlConnection);
That resulted in strange behaviour.
Fixed it by moving the code to the Context OnConfiguring
var sqlConnection = new SqlConnection(Startup.Configuration.GetConnectionString("xxx"));
optionsBuilder.UseSqlServer(sqlConnection);
I have a Xamarin based application which uses the Microsoft.OneDriveSDK nuget Package with version 1.x In this application I manage the OAuth stuff using Xamarin.Auth and thus get the access_token from that framework.
With the OneDriveSDK 1.x, I could provide this access token by redefining a few classes and then never had the API trying to fetch the token.
Now I wanted to migrate to version 2 and noticed that the previous classes got replaced and the API now uses Microsoft.Graph nuget package instead. So I had to implement the interface IAuthenticationProvider and did it like this:
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
if (!string.IsNullOrEmpty(MicrosoftLiveOAuthProvider.Instance.AccessToken))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", MicrosoftLiveOAuthProvider.Instance.AccessToken);
}
}
The code is called and properly provides the authentication token to the request headers. But once the SDK tries to use the token, I get an exception:
Exception of type 'Microsoft.Graph.ServiceException' was thrown.
Code: InvalidAuthenticationToken
Message: CompactToken parsing failed with error code: -2147184118
Now using google for this message always said the token is not JWT compliant and the SDK would then use it as microsoft live account token. But if this is the case, I wonder why it fails with V2 but works with V1.
Authentication is done against:
https://login.live.com/oauth20_authorize.srf
Any help is very mich appreciated!
I use a subclassed Xamarin.Auth WebRedirectAuthenticator with Microsoft.OneDriveSDK v2.0.0.
I get the initial access_token via that Xamarin.Auth subclass using a authorizeUrl: that is built via:
string GetAuthorizeUrl()
{
var requestUriStringBuilder = new StringBuilder();
requestUriStringBuilder.Append(Consts.MicrosoftAccountAuthenticationServiceUrl);
requestUriStringBuilder.AppendFormat("?{0}={1}", Consts.RedirectUriKeyName, Consts.Redirect_URI);
requestUriStringBuilder.AppendFormat("&{0}={1}", Consts.ClientIdKeyName, Consts.Client_ID);
requestUriStringBuilder.AppendFormat("&{0}={1}", Consts.ResponseTypeKeyName, Consts.TokenKeyName);
requestUriStringBuilder.AppendFormat("&{0}={1}", Consts.ScopeKeyName, Consts.Drive_Scopes);
return Uri.EscapeUriString(requestUriStringBuilder.ToString());
}
Once I have the access and refresh tokens, I can implement the IHttpProvider that needs passed to the OneDriveClient constructor in order to set access token in the http header:
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
SetupHttpClient();
return _httpClient.SendAsync(request);
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
SetupHttpClient();
return _httpClient.SendAsync(request, completionOption, cancellationToken);
}
HttpClient _httpClient;
void SetupHttpClient()
{
if (_httpClient == null)
{
_httpClient = new HttpClient();
var accessToken = _account.Properties["access_token"];
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
}
Create your OneDriveClient client using your IAuthenticationProvider and IHttpProvider objects (I just implement them on the same class that I am building all the OneDrive API calls), and every OnDrive request will use the access token from your saved Account.
Note: My IAuthenticationProvider implementation of AuthenticateRequestAsync currently does nothing, but you could do your Account setup here for a cleaner code flow.
var oneDriveClient = new OneDriveClient("https://api.onedrive.com/v1.0", this, this);
var pictureFolderItem = await oneDriveClient.Drive.Root.ItemWithPath("Pictures").Request().GetAsync();
Console.WriteLine(pictureFolderItem.Folder);
Refreshing is almost as easy, I store when the access token will expire (minus 5 minutes) and setup a timer to refresh it and re-save it to the Account. Do the same thing on app launch, if the user has an Account available and thus previously logged in, check if it is expired, refresh it, setup the background timer...
async Task<bool> GetRefreshToken(Account account)
{
// https://github.com/OneDrive/onedrive-api-docs/blob/master/auth/msa_oauth.md#step-3-get-a-new-access-token-or-refresh-token
OneDriveOAuth2Authenticator auth = OAuth2Authenticator();
var token = account.Properties["refresh_token"];
var expiresIn = await auth.RequestRefreshTokenAsync(token);
ResetRefreshTokenTimer(expiresIn);
return true;
}