Azure Rest API usage with SAS Service - azure

I am trying to use the Azure REST API with SAS Service. While I got this working with the most basic GET command, I am having trouble with the setup as soon as I need to add variables to the call as they are added at the same place as the SAS token. e.g. for "List Containers" I should use the URL "https://myaccount.blob.core.windows.net/?comp=list". But the "?comp=list" part is that the same place as the SAS Token. How can I give the request both the tokens and the variables? (I do not have much experience with REST APIs, so maybe I am misunderstanding something). I also posted my code below.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
namespace ConsoleProgram
{
public class DataObject
{
public string Name { get; set; }
}
public class Class1
{
private const string URL = "url";
private static string urlParameters = "?token";
static void Main(string[] args)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(URL);
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// List data response.
HttpResponseMessage response = client.GetAsync(urlParameters+ "&comp=list").Result; // Blocking call! Program will wait here until a response is received or a timeout occurs.
if (response.IsSuccessStatusCode)
{
Console.WriteLine(response.ToString());
// Parse the response body.
var dataObjects = response.Content.ReadAsStringAsync().Result; //Make sure to add a reference to System.Net.Http.Formatting.dll
Console.WriteLine("{0}", dataObjects);
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
// Make any other calls using HttpClient here.
// Dispose once all HttpClient calls are complete. This is not necessary if the containing object will be disposed of; for example in this case the HttpClient instance will be disposed automatically when the application terminates so the following call is superfluous.
client.Dispose();
}
}
}

When we use the SAS token to call Azure blob rest API, the SAS token is used as the query string. So we can use '&' to splice SAS token and other query parameters, such as https://myaccount.blob.core.windows.net/?comp=list&{sasToken}.
Besides, please note that if you want to list containers in one storage account, you need to create an account SAS token. The service SAS token cannot implement it. Regarding how to create the account SAS token, please refer to here

Related

Azure Function - Call Google API from within Azure Function (C#)

I am attempting to create an Azure Function using .NET Core to call to the YouTube API to retrieve some metrics on my videos.
Before calling the API I need to Authenticate with Google in a server to server method since this function will run daily with NO user interaction.
I've followed a number of examples (https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth) and I'm having no luck getting properly authenticated when running from Azure.
Is this possible? And can anyone point me to an example of this working?
For server-to-server interactions you need a service account, which is an account that belongs to your application instead of to an individual end-user. Your application calls Google APIs on behalf of the service account, and user consent is not required.
public class Program
{
// A known public activity.
private static String ACTIVITY_ID = "z12gtjhq3qn2xxl2o224exwiqruvtda0i";
public static void Main(string[] args)
{
Console.WriteLine("Plus API - Service Account");
Console.WriteLine("==========================");
String serviceAccountEmail = "SERVICE_ACCOUNT_EMAIL_HERE";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { PlusService.Scope.PlusMe }
}.FromCertificate(certificate));
// Create the service.
var service = new PlusService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Plus API Sample",
});
Activity activity = service.Activities.Get(ACTIVITY_ID).Execute();
Console.WriteLine(" Activity: " + activity.Object.Content);
Console.WriteLine(" Video: " + activity.Object.Attachments[0].Url);
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
The above sample code creates a ServiceAccountCredential. The required scopes are set and there is a call to FromCertificate, which loads the private key from the given X509Certificate2. As in all other samples code, the credential is set as HttpClientInitializer.
For more details about service account flow you could refer to this article.

Error "Exception while executing function" from Azure Service Bus Listener

We use an Azure Service Bus to post all of our requests from our Xamarin mobile app. The Azure Service Bus is bound to an Azure Function which is triggered each time a requests hits the Azure Service Bus.
We have found that we are getting errors from this Azure Function when we send data above a certain size. We can send up to 800 records without a problem but when we send >=850 records we get the following error:
[Error] Exception while executing function:
Functions.ServiceBusQueueTrigger. mscorlib: Exception has been thrown
by the target of an invocation. mscorlib: One or more errors occurred.
A task was canceled.
The service that is being invoked is an ASP.NET Web API RESTful service that saves the data records into a database. This doesn't generate any errors at all.
Here is my Azure Function code.
#r "JWT.dll"
#r "Common.dll"
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.ServiceBus.Messaging;
public static void Run(BrokeredMessage message, TraceWriter log)
{
log.Info($"C# ServiceBus queue trigger function processed message: {message.MessageId}");
if (message != null)
{
Common.Entities.MessageObjectEntity messageObject = message?.GetBody<Common.Entities.MessageObjectEntity>();
string msgType = messageObject?.MessageType;
var msgContent = messageObject?.MessageContent;
log.Info($"Message type: {msgType}");
double timestamp = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
string subscriber = "MYSUBSCRIBER";
string privatekey = "MYPRIVATEKEY";
Dictionary<string, object> payload = new Dictionary<string, object>()
{
{"iat", timestamp},
{"subscriber", subscriber}
};
string token = JWT.JsonWebToken.Encode(payload, privatekey, JWT.JwtHashAlgorithm.HS256);
using (var client = new HttpClient())
{
string url = $"http://myexamplewebservices.azurewebsites.net/api/routingtasks?formname={msgType}";
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(subscriber, token);
HttpContent content = new StringContent((string)msgContent, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.PostAsync(new Uri(url), content);
if (response == null)
{
log.Info("Null response returned from request.");
}
else
{
if (response.Result.IsSuccessStatusCode && response.Result.StatusCode == System.Net.HttpStatusCode.OK)
{
log.Info("Successful response returned from request.");
}
else
{
log.Info($"Unsuccessful response returned from request: {response.Result.StatusCode}.");
}
}
}
log.Info("Completing message.");
}
}
This code has been working for several years and works across all our other apps / web sites.
Any ideas why we're getting errors wehen we post large amounts of data to our Azure Service Bus / Azure Function?
It may caused by "new httpclient", there is a limit to how quickly system can open new sockets so if you exhaust the connection pool, you may get some errors. You can refer to this link: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
And could you please share some more error message ?
I can see that you are creating httpclient connection on each request which possibly be causing this issue. Httpclient creates a socket connection underneath it and has hard limit on it. Even when you dispose it it remains there for couple of mins that can't be used. A good practice is to create single static httpclient connection and reuse it. I am attaching some documents for you to go through.
AzFunction Static HttpClient , Http Client Working , Improper instantiation

Azure App Service with websockets and AD authentication

we got an application deployed as App Service and we are using SignalR for communication. After enabling AAD authentication - in browsers we started receiving 302 responses with redirect location to Azure AD.
Seems like the authentication layer on App Service is ignoring access_token passed by query string.
Request
Request URL: wss://<url>/hubs/chat?access_token=<token>
Request Method: GET
Response
Status Code: 302 Redirect
Location: https://login.windows.net/common/oauth2/authorize?...
After looking everywhere we couldn't find any solution to make this work.
The only solution to this issue that we see is either to disable authentication on App Service or use Long-Pooling, but both options are not acceptable in our situation.
By default, you web application will not get the access token from query string. Commonly, it will get the access token from authorization header or the cookie.
To get the access token from query string, you need to implement your custom authentication way.
Install Microsoft.Owin.Security.ActiveDirectory NuGet package.
Create an authentication provider which will get access token from query string.
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
Add map in .
app.Map("/yourpath", map =>
{
map.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Provider = new QueryStringOAuthBearerProvider(),
Tenant = tenantId,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = clientId
}
});
map.RunSignalR(hubConfiguration);
});
After multiple calls with Microsoft Technical Support, MS confirmed that App Service Authentication layer doesn't support access token passed in query string and there are no plans for this support yet. So there are two options:
Use different protocol for SignalR (long pooling works just fine)
Drop App Service Authentication
Using a custom middleware, I was able to update the request prior to authorization occurring:
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace Stackoverflow.Example.Security.Middleware
{
public class BearerTokenFromQueryToHeaderMiddleware
{
private readonly RequestDelegate _next;
public BearerTokenFromQueryToHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Query["access_token"];
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", $"Bearer {token}");
}
await _next(context);
}
}
}
I didn't try to get this working with the OpenID framework, but I did test using a custom policy. As long as this is registered earlier than the authentication, then this middleware should execute prior to the framework looking for the token in the header.

Postman not connecting to Dynamics365 API using Oauth client credentials, console app working using same details

I am trying to connect with Postman to Dynamics365 CRM REST API.
Although I obtain a token successfully without login using Grant Type client credentials I then get a 401 error when doing a sample GET to the API.
My console application is working successfully however and is not prompting the user for login (I don't want a login prompt to appear).
I have:
1. Registered the app in Azure AD,
2. Created the client secret
3. Created the application user in Dynamics and linked via the Application ID to App from step 1
I've done this with two different apps and two different application users and get the same result in Postman i.e. Token retrieved but 401 error on GET.
The console app below is working with the same credentials, would appreciate any input on what is missing in the Postman config
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http.Headers;
using System.Net.Http;
using Newtonsoft.Json.Linq;
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
const string ResourceId = "https://myurldev.crm4.dynamics.com/api/data/v9.1";
Task<AuthenticationResult> t = GetUserOAuthToken();
t.Wait();
string accessToken = t.Result.AccessToken;
Console.WriteLine("ACCESS TOKEN \n\n" + accessToken);
Console.WriteLine("\n\n Please any key to display content of the blob");
//Console.ReadKey();
using (HttpClient httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(ResourceId);
httpClient.Timeout = new TimeSpan(0, 2, 0); // 2 minutes
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
//Send the Get Incident request to the Web API using a GET request.
var response = httpClient.GetAsync("/incidents",
HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
JObject body = JObject.Parse(response.Content.ReadAsStringAsync().Result);
string title = (string)body["title"];
Console.WriteLine("Incident title is : {0}", title);
}
else
{
Console.WriteLine("The request failed with a status of '{0}'",
response.ReasonPhrase);
}
}
/*
// Use the access token to create the storage credentials.
TokenCredential tokenCredential = new TokenCredential(accessToken);
StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
// Create a block blob using those credentials
CloudBlockBlob blob = new CloudBlockBlob(new Uri("https://placeholderURL/SalesOrder.json"), storageCredentials);
using (var stream = blob.OpenRead())
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
Console.WriteLine(reader.ReadLine());
}
}
}
Console.WriteLine("\n\n Please any key to terminate the program");
Console.ReadKey();*/
}
static async Task<AuthenticationResult> GetUserOAuthToken()
{
const string ResourceId = "https://myurldev.crm4.dynamics.com/";
const string AuthInstance = "https://login.microsoftonline.com/{0}/";
const string TenantId = "XXXX-DYNAMICS_TENANT_ID-XXXXX"; // Tenant or directory ID
// Construct the authority string from the Azure AD OAuth endpoint and the tenant ID.
string authority = string.Format(System.Globalization.CultureInfo.InvariantCulture, AuthInstance, TenantId);
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential cc = new ClientCredential("XXXX_APPLICATION_ID_XXXX", "XXXXX_CLIENT_SECRET_XXXX");
// Acquire an access token from Azure AD.
AuthenticationResult result = await authContext.AcquireTokenAsync(ResourceId, cc);
return result;
}
}
}
POSTMAN TOKEN VARIABLES
POSTMAN REQUEST AND RESPONSE
I had already added the following permissions to my APP
This is the response when I analyse the token in JWT.io
It seems that Dynamics CRM only support delegated permission which allow the application to access Common Data Service acting as users in the organization. This means client credentials is not appropriate here.
However, you said that you can use client credentials in console app. You can try with below request to get the access token.

Validate a token signature for subsequent request in a restful web api

When the user is authenticated I put a signed token in the response authorization header.
Every furthere access on a ressource url is only allowed with a valid signed token.
When I create the token and valdiate it:
var principal = tokenHandler.ValidateToken(tokenString, validationParameters);
then I get the principal (user who made the request) when the signed key is the same which got
used by creating the token.
That I can use the same signed key after authentication and during the ressource request to validate the token I have created this class:
public static class ApiConstants
{
private static readonly RNGCryptoServiceProvider CryptoProvider = new RNGCryptoServiceProvider(new byte[33]);
private static byte[] key = new byte[32];
static ApiConstants()
{
CryptoProvider.GetBytes(key);
}
public static byte[] GetSignedKey()
{
return key;
}
}
Is there anything wrong that I put this code in a static class which is actually my full purpose as I want the filling up of the byte array with random numbers to happen only one time!?
Is there still something I can improve?
You Can't make the signature token as static. because then it will become as global variable and shared by all request(thread). Also you will face concurrency issue with static field.
If you want to make it session specific. then you need to store in a session variable not in a static field.

Resources