I would like to utilize a function app to make a string of sequential api calls to download a file to a storage account daily. I am utilizing the first function to use the auth code to retrieve a refresh token to then add to a storage table (could utilize a key vault as well) which works correctly (AuthHTTPTrigger). From this point I need to pass the stored refresh token into the next api call to receive an access token and update the new refresh token that is returned into the storage table. The next api call passes in the access token to create said report. This call now generates a reportId that needs to be passed into the last api call url alongside the access token which eventually generates a report via a secured link (will essentially use a timer to refresh the call until param is not null).
Is there a simple way to make sequential api calls due to each call being dependent on the previous to pass in parameters? Create another function and separate tasks for each api call utilizing await task and await all and a timer to await the download link of the file are my current thoughts.
Ultimately I want to kick off this function once manually and utilize the refresh token as the access token expires daily while updating the refresh token daily.
- https://partnercenterxxx.azurewebsites.net/api/AuthHttpTrigger - requires auth code that requires manual interaction - produces an access token, refresh token, and id token
- https://login.windows.net/{app id}/oauth2/token - pass in refresh token to provide new access token
- https://api.partnercenter.microsoft.com/insights/v1/mpn/ScheduledReport pass in access token to create report - generates report id
- https://api.partnercenter.microsoft.com/insights/v1/mpn/ScheduledReport/execution/{report id} - pass in access token from previous call and generated report it
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace PartnerCenterAPI.Properties
{
public static class AuthHttpTrigger
{
[FunctionName("AuthHttpTrigger")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
if (log is null)
{
throw new ArgumentNullException(nameof(log));
}
// Get the authentication code from the request payload
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
string authCode = data.authCode;
// Get the Application details from the settings
string tenantId = Environment.GetEnvironmentVariable("TenantId", EnvironmentVariableTarget.Process);
string clientId = Environment.GetEnvironmentVariable("ClientId", EnvironmentVariableTarget.Process);
string clientSecret = Environment.GetEnvironmentVariable("ClientSecret", EnvironmentVariableTarget.Process);
string storageConnectionString = Environment.GetEnvironmentVariable("TableStorage", EnvironmentVariableTarget.Process);
// Get the access and refresh token from MS Identity
MicrosoftIdentityClient idClient = new(clientId, clientSecret, tenantId);
(string idToken, string accessToken, string refreshToken) = await idClient.GetAccessTokenFromAuthorizationCode(authCode);
// Save the refresh token to an Azure Storage Table
AzureStorageClient azureStorageClient = new(storageConnectionString);
await azureStorageClient.AddOrUpdateRefreshToken(clientId, MicrosoftIdentityClient.GetIdTokenUniqueUser(idToken), refreshToken);
return new OkObjectResult(new ReturnValue
{
AccessToken = accessToken,
IdToken = idToken,
RefreshToken = refreshToken,
});
}
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace PartnerCenterAPI.Properties
{
public class MicrosoftIdentityClient
{
private static readonly HttpClient httpClient = new();
private static readonly string hostUrl = "https://login.microsoftonline.com";
private readonly string tenantId;
private readonly string clientId;
private readonly string clientSecret;
public MicrosoftIdentityClient(string clientId, string clientSecret, string tenantId)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenantId = tenantId;
}
public static string GetIdTokenUniqueUser(string idToken)
{
JwtSecurityToken securityToken = new(idToken);
string tid = securityToken.Payload.Claims.FirstOrDefault(claim => claim.Type == "tid").Value;
string sub = securityToken.Payload.Claims.FirstOrDefault(claim => claim.Type == "sub").Value;
return Convert.ToBase64String(Encoding.UTF8.GetBytes($"{tid}{sub}"));
}
public async Task<(string idToken, string accessToken, string refreshToken)> GetAccessTokenFromAuthorizationCode(string authCode)
{
string redirectUrl = "https://partnercenterxxxx.azurewebsites.net";
string scopes = "openid offline_access https://api.partnercenter.microsoft.com/user_impersonation";
Uri requestUri = new($"{hostUrl}/{tenantId}/oauth2/v2.0/token");
List<KeyValuePair<string, string>> content = new()
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("scope", scopes),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", authCode),
new KeyValuePair<string, string>("redirect_uri", redirectUrl),
new KeyValuePair<string, string>("client_secret", clientSecret)
};
HttpRequestMessage request = new(HttpMethod.Post, requestUri)
{
Content = new FormUrlEncodedContent(content),
};
HttpResponseMessage response = await httpClient.SendAsync(request);
string responseContent = await response.Content.ReadAsStringAsync();
dynamic responseObject = JsonConvert.DeserializeObject(responseContent);
if (response.IsSuccessStatusCode)
{
// dynamic values need to be assigned before passing back
return (responseObject.id_token, responseObject.access_token, responseObject.refresh_token);
}
else if (response.StatusCode == HttpStatusCode.BadRequest)
{
// Something failed along the way, and there will be an error in there if the error code is 400
// Handle it however you want.
throw new Exception((string)responseObject.error_description);
}
else
{
// ¯\_(ツ)_/¯
throw new Exception("Something bad happened");
}
}
public async Task<(string idToken, string accessToken, string refreshToken)> GetAccessTokenFromRefreshToken(string refreshToken)
{
string scopes = "openid offline_access https://api.partnercenter.microsoft.com/user_impersonation";
Uri requestUri = new($"{hostUrl}/{tenantId}/oauth2/v2.0/token");
List<KeyValuePair<string, string>> content = new()
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("scope", scopes),
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("refresh_token", refreshToken),
new KeyValuePair<string, string>("client_secret", clientSecret)
};
HttpRequestMessage request = new(HttpMethod.Post, requestUri)
{
Content = new FormUrlEncodedContent(content),
};
HttpResponseMessage response = await httpClient.SendAsync(request);
string responseContent = await response.Content.ReadAsStringAsync();
dynamic responseObject = JsonConvert.DeserializeObject(responseContent);
if (response.IsSuccessStatusCode)
{
return (responseObject.id_token, responseObject.access_token, responseObject.refresh_token);
}
else if (response.StatusCode == HttpStatusCode.BadRequest)
{
// Something failed along the way, and there will be an error in there if the error code is 400
// Handle it however you want.
throw new Exception((string)responseObject.error_message);
}
else
{
// ¯\_(ツ)_/¯
throw new Exception("Something bad happened");
}
}
}
}
using Microsoft.Azure.Cosmos.Table;
using System.Threading.Tasks;
namespace PartnerCenterAPI.Properties
{
public class AzureStorageClient
{
private readonly CloudTable refreshTokenTable;
public AzureStorageClient(string connectionString)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration());
refreshTokenTable = tableClient.GetTableReference("refreshTokens");
refreshTokenTable.CreateIfNotExists();
}
public async Task<RefreshTokenEntity> AddOrUpdateRefreshToken(string audience, string userId, string refreshToken)
{
RefreshTokenEntity tokenEntity = new(audience, userId)
{
RefreshToken = refreshToken
};
TableResult tableResult = await refreshTokenTable.ExecuteAsync(TableOperation.InsertOrReplace(tokenEntity));
return tableResult.Result as RefreshTokenEntity;
}
}
}
using Microsoft.Azure.Cosmos.Table;
using System;
namespace PartnerCenterAPI.Properties
{
public class RefreshTokenEntity : TableEntity
{
public string RefreshToken { get; set; }
public RefreshTokenEntity(string audience, string userId)
{
RefreshTokenEntity refreshTokenEntity = this;
refreshTokenEntity.PartitionKey = audience;
refreshTokenEntity.RowKey = userId;
}
public static implicit operator string(RefreshTokenEntity v)
{
throw new NotImplementedException();
}
}
}