8

I created 2 Azure Function Apps, both setup with Authentication/Authorization so an AD App was created for both. I would like to setup AD Auth from one Function to the other using MSI. I setup the client Function with Managed Service Identity using an ARM template. I created a simple test function to get the access token and it returns: Microsoft.Azure.Services.AppAuthentication: Token response is not in the expected format.

try {
    var azureServiceTokenProvider = new AzureServiceTokenProvider();
    string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://myapp-registration-westus-dev.azurewebsites.net/");
    log.Info($"Access Token: {accessToken}");
    return req.CreateResponse(new {token = accessToken});
}
catch(Exception ex) {
    log.Error("Error", ex);
    throw;
}

2 Answers2

2

Yes, there is a way to do this. I'll explain at a high level, and then add an item to the MSI documentation backlog to write a proper tutorial for this.

What you want to do is follow this Azure AD authentication sample, but only configure and implement the parts for the TodoListService: https://github.com/Azure-Samples/active-directory-dotnet-daemon.

The role of the TodoListDaemon will be played by a Managed Service Identity instead. So you don't need to register the TodoListDaemon app in Azure AD as instructed in the readme. Just enable MSI on your VM/App Service/Function.

In your code client side code, when you make the call to MSI (on a VM or in a Function or App Service), supply the TodoListService's AppID URI as the resource parameter. MSI will fetch a token for that audience for you.

The code in the TodoListService example will show you how to validate that token when you receive it.

So essentially, what you want to do is register an App in Azure AD, give it an AppID URI, and use that AppID URI as the resource parameter when you make the call to MSI. Then validate the token you receive at your service/receiving side.

skwan
  • 169
  • 4
1

Please check that the resource id used "https://myapp-registration-westus-dev.azurewebsites.net/" is accurate. I followed steps here to setup Azure AD authentication, and used the same code as you, and was able to get a token. https://learn.microsoft.com/en-us/azure/app-service/app-service-mobile-how-to-configure-active-directory-authentication

You can also run this code to check the exact error returned by MSI. Do post the error if it does not help resolve the issue.

HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Secret", Environment.GetEnvironmentVariable("MSI_SECRET"));
var response = await client.GetAsync(String.Format("{0}/?resource={1}&api-version={2}", Environment.GetEnvironmentVariable("MSI_ENDPOINT"), "https://myapp-registration-westus-dev.azurewebsites.net/", "2017-09-01"));
string msiResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
log.Info($"MSI Response: {msiResponse}");

Update:- This project.json file and run.csx file work for me. Note: The project.json refers to .NET 4.6, and as per Azure Functions documentation (link in comments), .NET 4.6 is the only supported version as of now. You do not need to upload the referenced assembly again. Most probably, incorrect manual upload of netstandard assembly, instead of net452 is causing your issue.

Only the .NET Framework 4.6 is supported, so make sure that your project.json file specifies net46 as shown here. When you upload a project.json file, the runtime gets the packages and automatically adds references to the package assemblies. You don't need to add #r "AssemblyName" directives. To use the types defined in the NuGet packages, add the required using statements to your run.csx file.

project.json

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.Azure.Services.AppAuthentication": "1.0.0-preview"
      }
    }
   }
}

run.csx

using Microsoft.Azure.Services.AppAuthentication;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    try
    {
        var azureServiceTokenProvider = new AzureServiceTokenProvider();
        string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net/");
        log.Info($"Access Token: {accessToken}");
        return req.CreateResponse(new {token = accessToken});
    }
    catch(Exception ex) 
    {
        log.Error("Error", ex);
        throw;
    }    
}
Varun Sharma
  • 568
  • 4
  • 5
  • "Microsoft.Azure.Services.AppAuthentication": "1.0.0-preview", – Brian Aabel Oct 04 '17 at 23:57
  • Using the client.GetAsync() with the same resource uri returns a valid token. In the Azure Function I Am using NuGet to resolve the AppAuthentication component and referencing with #r "..\bin\..." . I am using this reference: **"Microsoft.Azure.Services.AppAuthentication": "1.0.0-preview"** Is this the latest version? Also, making subsequent calls to the function causes GetAccesstokenAsync() to hang and never return. – Brian Aabel Oct 05 '17 at 00:08
  • Can you please share the response you get from client.GetAsync() - the "MSI Response" that gets logged? Do replace the access token with a placeholder (e.g. "eyJ0eXAi...") . Just want to make sure the response format is as expected. The NuGet version is correct, and that is the only version. – Varun Sharma Oct 05 '17 at 00:53
  • MSI Response: {"access_token":"eyJ0eXAiOiJKV1QiLCJhtwk9uc9sNTVr9ado8JflIpBU4drh0tN7Wue5-HBzcuQGCWmMYPcFoQm-uI7R6wP0WrkayVBD4RPY_CJ8o4HfOLwQ01qJhazujpv3xaAsg.......","expires_on":"10/5/2017 2:27:54 AM +00:00","resource":"https://MASKED-registration-westus-dev.azurewebsites.net","token_type":"Bearer"} – Brian Aabel Oct 05 '17 at 01:31
  • 2017-10-05T01:39:06.375 System.FormatException : Token response is not in the expected format. at Microsoft.Azure.Services.AppAuthentication.TokenResponse.Parse(String tokenResponse) at async Microsoft.Azure.Services.AppAuthentication.MsiAccessTokenProvider.GetTokenAsync(String resource,String authority) at async Microsoft.Azure.Services.AppAuthentication.NonInteractiveAzureServiceTokenProviderBase.GetTokenAsync(??) at async Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider.GetAccessTokenAsyncImpl(String authority,String resource,String scope) at async – Brian Aabel Oct 05 '17 at 01:40
  • Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider.GetAccessTokenAsync(String resource,String tenantId) at async Submission#0.Get(HttpRequestMessage req,String registrationId,TraceWriter log) at D:\home\site\wwwroot\get-registration\run.csx : 59 2017-10-05T01:39:06.393 Exception while executing function: Functions.get-registration. Microsoft.Azure.WebJobs.Script: One or more errors occurred. Microsoft.Azure.Services.AppAuthentication: Token response is not in the expected format. – Brian Aabel Oct 05 '17 at 01:40
  • I don't use a placeholder. The above is the stacktrace from the error when using the AppAuthentication component to get the token. – Brian Aabel Oct 05 '17 at 01:41
  • Unfortunately, the MSI response you entered does not render properly. I have updated the answer to show the de-serialization error. Please try that. – Varun Sharma Oct 05 '17 at 06:47
  • The updated code runs without exception and I get a valid token and deserialized object. – Brian Aabel Oct 05 '17 at 11:19
  • **The same exception happens using KeyVault:** var azureServiceTokenProvider = new AzureServiceTokenProvider(); var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); await kv.GetSecretAsync("https://my-vault.vault.azure.net/secrets/main-api-dev/3c36195c68a54bbcbe4606e5615c9211"); – Brian Aabel Oct 05 '17 at 11:44
  • Are you by any chance targeting .NET Core? I was able to reproduce this issue when I set netcoreapp1.1 as the target in project.json. AppAuthentication library targets netstandard 1.4, and so this may be resulting in the same issue as described [here](https://github.com/Azure/azure-webjobs-sdk-script/issues/1514). Can you please tell me what framework are you targeting? Thanks! – Varun Sharma Oct 05 '17 at 15:27
  • .NET Framework version v4.7, Azure FUnctions Runtime version: 1.0.11247.0 (latest). – Brian Aabel Oct 05 '17 at 15:47
  • When using project.json, as per the [documentation](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#using-nuget-packages), only .NET 4.6 is supported as of now. _When you upload a project.json file, the runtime gets the packages and automatically adds references to the package assemblies. You don't need to add #r "AssemblyName" directives_. Since you manually uploaded the AppAuthentication.dll, most likely you uploaded the .netstandard one, instead of the net452 one, which is causing this issue. – Varun Sharma Oct 05 '17 at 19:34
  • Sorry, the .net version I was referring to was the one Azure Functions Runtime uses. We have a build process that runs NuGet before uploading to the site and we upload a \bin folder with the dependencies during the deployment. The version in our project.json is "frameworks": { "net46":{ "dependencies": { "Newtonsoft.Json.Schema" : "2.0.8", "Microsoft.Azure.Services.AppAuthentication": "1.0.0-preview" – Brian Aabel Oct 05 '17 at 19:39
  • I updated the answer and included project.json and run.csx. This does not look like a library issue. I have taken an action item to emit the actual exception in the library, to make it easier to troubleshoot. – Varun Sharma Oct 05 '17 at 19:47
  • Thanks. I created a project.json in the Function folder and referenced the library and it is now working. – Brian Aabel Oct 06 '17 at 14:11