/// <summary> /// Unpack the token data from the API Authentication or Refresh calls /// </summary> /// <param name="content">reponse string containing the data </param> /// <param name="isRefresh">Property used to know if the Unpack is from the refresh</param> /// <returns></returns> private XeroAccessToken UnpackToken(string content, bool isRefresh) { // Record the token data var tokens = JObject.Parse(content); XeroAccessToken newToken = new XeroAccessToken(); newToken.AccessToken = tokens["access_token"]?.ToString(); newToken.ExpiresAtUtc = DateTime.Now.AddSeconds(int.Parse(tokens["expires_in"]?.ToString())); newToken.RefreshToken = tokens["refresh_token"]?.ToString(); if (XeroConfig != null) { if (!isRefresh) { // Only bother with this if its not a refresh if (XeroConfig.StoreReceivedScope) { newToken.RequestedScopes = tokens["scope"]?.ToString(); // Ensure we record the scope used } else { newToken.RequestedScopes = XeroConfig.Scope; } } else { // Ensure the scopes list is left intact! newToken.RequestedScopes = XeroConfig.XeroAPIToken.RequestedScopes; } } return(newToken); }
/// <summary> /// Perform a refresh. /// </summary> /// <param name="clientID">The client ID to use (optional) if not supplied the data in the Config will be used</param> /// <param name="oldToken">The client ID to use (optional) if not supplied the data the Config will be used</param> /// <param name="secret">The client secret to use for CODE style (optional) if not supplied the data in the Config will be used</param> /// <returns>New Token record</returns> public XeroAccessToken RefreshToken(string clientID = null, XeroAccessToken oldToken = null, string secret = null) { try { using (var client = new HttpClient()) { FormUrlEncodedContent formContent = null; if ((XeroConfig != null && !string.IsNullOrEmpty(XeroConfig.ClientSecret)) || secret != null) { // CODE string encoded = null; if (!string.IsNullOrEmpty(clientID) && !string.IsNullOrEmpty(secret)) { // Used passed ID encoded = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientID}:{secret}")); } else { // otherwise use ID from config encoded = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{XeroConfig.ClientID}:{XeroConfig.ClientSecret}")); } client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encoded); formContent = new FormUrlEncodedContent(new[] { new KeyValuePair <string, string>("grant_type", "refresh_token"), new KeyValuePair <string, string>("refresh_token", oldToken != null ? oldToken.RefreshToken : XeroConfig.XeroAPIToken.RefreshToken), }); } else { // PKCE formContent = new FormUrlEncodedContent(new[] { new KeyValuePair <string, string>("grant_type", "refresh_token"), new KeyValuePair <string, string>("client_id", clientID != null ? clientID : XeroConfig.ClientID), new KeyValuePair <string, string>("refresh_token", oldToken != null ? oldToken.RefreshToken : XeroConfig.XeroAPIToken.RefreshToken), }); } var response = Task.Run(() => client.PostAsync(XeroConstants.XERO_TOKEN_URL, formContent)).ConfigureAwait(false).GetAwaiter().GetResult(); if (response.StatusCode == System.Net.HttpStatusCode.OK) { var content = Task.Run(() => response.Content.ReadAsStringAsync()).ConfigureAwait(false).GetAwaiter().GetResult(); // Unpack the response tokens if (content.Contains("error")) { throw new Exception(content); } // Only relevent for a new Auth - Refresh can be actioned with clientid and refresh token if (XeroConfig != null) { XeroConfig.XeroAPIToken = UnpackToken(content, true); } return(UnpackToken(content, true)); } else { // Something didnt work - disconnected/revoked? throw new Exception(response.ReasonPhrase); } } } catch (Exception ex) { throw new InvalidDataException($"Refresh Exchange Failed - {ex.Message}"); } }