Custom authorisation of an Azure Function - azure

I've set up my Azure Function and I can see that there are options to support Azure Active Directory for authentication, which looks great. In a previous project I have used .NET Core to host a WebAPI and subsequently used Authorisation Policies (https://learn.microsoft.com/en-us/aspnet/core/security/authorization/) to provide fine grained claims based authorisation in my API. I cannot seem to find an equivalent mechanism in an Azure Function.
Can anyone tell me if there is a way to do this sort of thing in an Azure Function?

There is not currently built-in support for fine-grained authorization. This would make a great suggestion item for the Functions UserVoice.
You could always write authorization logic as a part of your function, although a built-in feature would certainly be better. The below code snippet (C#) does an authentication check in code and prints a list of claims. You could modify it to require specific claims:
using System.Net;
using System.Threading;
using System.Security.Claims;
 
public static void Run(HttpRequestMessage req, TraceWriter log)
{
    if (!Thread.CurrentPrincipal.Identity.IsAuthenticated)
    {
        log.Info("Not authenticated");
        return req.CreateResponse(HttpStatusCode.Unauthorized);
    }
   
    ClaimsIdentity identity = (Thread.CurrentPrincipal as ClaimsPrincipal)?.Identity as ClaimsIdentity;
if (identity != null)
{
    foreach (var claim in identity.Claims)
    {
       log.Info($"{claim.Type} = {claim.Value}");
    }
}
// Rest of your function...
return req.CreateResponse(HttpStatusCode.OK);
}
Note that in non-.NET languages, you would want to inspect the headers for claims information. You could also combine this with calls to the /.auth/me endpoint and provider graph endpoints.

Related

Get Azure AD Groups Before Building Authorization Policies

We're developing an application that uses a back-end built on .Net Core 2.2 Web API. Most of our controllers merely require the [Authorize] attribute with no policy specified. However, some endpoints are going to require the user to be in a particular Azure AD Security Group. For those cases, I implemented policies like this in the Startup.cs file:
var name = "PolicyNameIndicatingGroup";
var id = Guid.NewGuid; // Actually, this is set to the object ID of the group in AD.
services.AddAuthorization(
options =>
{
options.AddPolicy(
name,
policyBuilder => policyBuilder.RequireClaim(
"groups",
id.ToString()));
});
Then, on controllers requiring this type of authorization, I have:
[Authorize("PolicyNameIndicatingGroup")]
public async Task<ResponseBase<string>> GroupProtectedControllerMethod() {}
The problem is that our users are all in a large number of groups. This causes the Graph API to return no group claims at all, and instead a simple hasGroups boolean claim set to true. Therefore, no one has any groups, and thus cannot pass authorization. This no-groups issue can be read about here.
This string-based policy registration, lackluster as it may be, seems to be what the .Net Core people are recommending, yet it falls flat if the groups aren't populated on the User Claims. I'm not really seeing how to circumnavigate the issue. Is there some special way to set up the AppRegistration for my API so that it does get all of the groups populated on the User Claims?
Update:
In the solution, I do have a service that calls Graph to get the user's groups. However, I can't figure out how to call it before it's too late. In other words, when the user hits the AuthorizeAttribute on the controller to check for the policy, the user's groups have not yet been populated, so the protected method always blocks them with a 403.
My attempt consisted of making a custom base controller for all of my Web API Controllers. Within the base controller's constructor, I'm calling a method that checks the User.Identity (of type ClaimsIdentity) to see if it's been created and authenticated, and, if so, I'm using the ClaimsIdentity.AddClaim(Claim claim) method to populate the user's groups, as retrieved from my Graph call. However, when entering the base controller's constructor, the User.Identity hasn't been set up yet, so the groups don't get populated, as previously described. Somehow, I need the user's groups to be populated before I ever get to constructing the controller.
I found an answer to this solution thanks to some tips from someone on the ASP.NET Core team. This solution involves implementing an IClaimsTransformation (in the Microsoft.AspNetCore.Authentication namespace). To quote my source:
[IClaimsTransformation] is a service you wire into the request pipeline which will run after every authentication and you can use it to augment the identity as you like. That would be where you’d do your Graph API call [...]."
So I wrote the following implementation (see an important caveat below the code):
public class AdGroupClaimsTransformer : IClaimsTransformation
{
private const string AdGroupsAddedClaimType = "adGroupsAlreadyAdded";
private const string ObjectIdClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
private readonly IGraphService _graphService; // My service for querying Graph
private readonly ISecurityService _securityService; // My service for querying custom security information for the application
public AdGroupClaimsTransformer(IGraphService graphService, ISecurityService securityService)
{
_graphService = graphService;
_securityService = securityService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;
var userIdentifier = FindClaimByType(claimsIdentity, ObjectIdClaimType);
var alreadyAdded = AdGroupsAlreadyAdded(claimsIdentity);
if (claimsIdentity == null || userIdentifier == null || alreadyAdded)
{
return Task.FromResult(principal);
}
var userSecurityGroups = _graphService.GetSecurityGroupsByUserId(userIdentifier).Result;
var allSecurityGroupModels = _securityService.GetSecurityGroups().Result.ToList();
foreach (var group in userSecurityGroups)
{
var groupIdentifier = allSecurityGroupModels.Single(m => m.GroupName == group).GroupGuid.ToString();
claimsIdentity.AddClaim(new Claim("groups", groupIdentifier));
}
claimsIdentity.AddClaim(new Claim(AdGroupsAddedClaimType, "true"));
return Task.FromResult(principal);
}
private static string FindClaimByType(ClaimsIdentity claimsIdentity, string claimType)
{
return claimsIdentity?.Claims?.FirstOrDefault(c => c.Type.Equals(claimType, StringComparison.Ordinal))
?.Value;
}
private static bool AdGroupsAlreadyAdded(ClaimsIdentity claimsIdentity)
{
var alreadyAdded = FindClaimByType(claimsIdentity, AdGroupsAddedClaimType);
var parsedSucceeded = bool.TryParse(alreadyAdded, out var valueWasTrue);
return parsedSucceeded && valueWasTrue;
}
}
Within my Startup.cs, in the ConfigureServices method, I register the implementation like this:
services.AddTransient<IClaimsTransformation, AdGroupClaimsTransformer>();
The Caveat
You may have noticed that my implementation is written defensively to make sure the transformation will not be run a second time on a ClaimsPrincipal that has already undergone the procedure. The potential issue here is that calls to the IClaimsTransformation might occur multiple times, and that might be bad in some scenarios. You can read more about this here.
You can use the Microsoft Graph API to query the user's groups instead:
POST https://graph.microsoft.com/v1.0/directoryObjects/{object-id}/getMemberGroups
Content-type: application/json
{
"securityEnabledOnly": true
}
Reference: https://learn.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http
The scenario will be:
Your client app will acquire access token (A) for accessing your back-end Web API.
Your Web API application will acquire access token (B) for accessing the Microsoft Graph API with the access token (A) using OAuth 2.0 On-Behalf-Of flow. Access token (B) will be used to get the user's groups.
Web API validates the user's group using a policy (recommended) or custom attribute.
The protocol diagram and sample request are listed in this article using the Azure AD V2.0 Endpoint. This article is for the V1.0 endpoint. Here are code samples for .Net Core.

Azure Function as a Web API performance and pricing

We are planning to build a web application, and I was hoping someone could help us to decide whether to use Azure App Service or Azure Function for providing rest API to the client side.
Our requirements are as follows.
Authentication and authorization
CRUD on Azure SQL and Cosmos DB
Multi region
100,000,000 API calls per month
At first, we were going to build the backend using Azure App Service. But after studying pros and cons on Azure Functions, Azure Functions became more appealing to us.
So is it even a good idea to build a web application that depends on Azure Functions as a REST API provider?
Does anyone have an experience building, managing and scaling up and out Azure Functions as a REST API provider?
Is it even a good idea to build a web application that depends on Azure Functions as a REST API provider?
It seems you are planning to have REST API using Web Service or Azure Function. Your decision is perfect I would say. For Azure Function its not mandatory to have web service for that. Azure function would be best option for you. You can implement all the feature that Web API provides. So if your target is only develop API then you can start with Azure Function with no other alternative. Its outstanding actually!
Does anyone have an experience building, managing and scaling up and out Azure Functions as a REST API provider?
I am working with Azure Function for our AI Base Bot with LUIS integration. From my understanding it's a very easily maintainable, fastest response time, you can build it from anywhere. So you can undoubtedly go with Azure function.
Why Choose Azure Function:
It's stateless, need not any server to run
Full REST, can call from anywhere any Region
Can develop both Azure Portal and local Visual Studio
Cost-effective, you can pay only how much you use.
Multiple language support
Easy Authorization and Authentication functionality
No limit of calling as per your consumption plan
Do A Lot With Azure Function:
You can develop a robust API service with Azure functions. It has many outstanding features. Please check Check here
Authorization and Authentication:
You can simply integrate your authorization and authentication on your function App. Even you can implement it on each function separately or on a full application. It supports most of the popular authentication provider for example:
Azure Active Directory
Microsoft Identity
Goggle
Facebook
Twitter auth
See how can you implement authentication:
Step:1
Step:2
Rest Function Code Sample:
Here I am giving you a simple code snippet to start with: though it's on Azure Table Storage, but help you to develop azure function and CRUD concept.
Your Sample Class:
public class YourSampleClass
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
}
Table Storage Class:
public class TableStorageClass
{
public TableStorageClass()
{
}
public TableStorageClass(DynamicTableEntity entity)
{
PartitionKey = entity.PartitionKey;
RowKey = entity.RowKey;
}
public string PartitionKey { get; set; }
public string RowKey { get; set; }
}
Azure Function V2 Example:
public static class FunctionReadFromTableStorage
{
[FunctionName("FunctionReadFromTableStorage")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
//Read Request Body
var content = await new StreamReader(req.Body).ReadToEndAsync();
//Extract Request Body and Parse To Class
YourSampleClass objYourSampleClass = JsonConvert.DeserializeObject<YourSampleClass>(content);
// Validate param because PartitionKey and RowKey is required to read from Table storage In this case , so I am checking here.
dynamic validationMessage;
if (string.IsNullOrEmpty(objYourSampleClass.PartitionKey))
{
validationMessage = new OkObjectResult("PartitionKey is required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objYourSampleClass.RowKey))
{
validationMessage = new OkObjectResult("RowKey is required!");
return (IActionResult)validationMessage;
}
// Table Storage operation with credentials
var client = new CloudTableClient(new Uri("https://YourStorageURL.table.core.windows.net/"),
new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials("YourStorageName", "xtaguZokAWbfYG4QDkBjT+YourStorageKey+T/kId/Ng+cl3TfYHtg=="));
var table = client.GetTableReference("YourTableName");
//Query filter
var query = new TableQuery()
{
FilterString = string.Format("PartitionKey eq '{0}' and RowKey eq '{1}'", objYourSampleClass.PartitionKey, objYourSampleClass.RowKey)
};
//Request for storage query with query filter
var continuationToken = new TableContinuationToken();
var storageTableQueryResults = new List<TableStorageClass>();
foreach (var entity in table.ExecuteQuerySegmentedAsync(query, continuationToken).GetAwaiter().GetResult().Results)
{
var request = new TableStorageClass(entity);
storageTableQueryResults.Add(request);
}
//As we have to return IAction Type So converting to IAction Class Using OkObjectResult We Even Can Use OkResult
var result = new OkObjectResult(storageTableQueryResults);
return (IActionResult)result;
}
}
Point To Remember:
In case of Azure Portal execution just get rid of FunctionReadFromTableStorage class
You Need following reference to execute above code
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;
using Microsoft.WindowsAzure.Storage.Table;
using System.Collections.Generic;
Postman Request Pattern:
Function Invoke Sample:
{
"PartitionKey": "Your Param According to Table Storage Design" ,
"RowKey": "Your Param According to Table Storage Design",
"Directory": "Your Param According to Table Storage Design"
}
See The Screen Shot:
Post Man Response:
Response is subject to my own table design
[
{
"partitionKey": "Microsoft SharePoint Server",
"rowKey": "2016"
}
]
See The Screen Shot Below:
Note: For CosmosDb Integration you could check here. Azure SQL with Function take a look here.

Is there a way to secure an Azure Function that will only be called from a specific Azure Logic App?

I understand that Azure Functions are potentially open endpoints on the internet if I read Microsoft’s documentation correctly and per conversations with a friend who has some experience working with web development paradigms that Azure Functions leverages. A cursory reading of security forums and stack overflow questions on the topic leads me to understand at least a couple options of securing them namely
Azure Active Directory
Shared Access Signatures (SAS) and
Azure Virtual Networks.
Context/ What does my Azure Function do? It manages a blob container related to an ETL of vendor data from a SFTP source to a SQL Endpoint which this ETL utilizes an intermediary blob container for file transfer and long term cold storage of source data. The Azure Function moves the blobs from one container to an archive container after they have been loaded to the SQL endpoint. Why Azure Function to manage the blob containers?
SSIS lacks ability to perform blob manipulation (i.e copy and delete)
Logic App lacks ability to perform a join (of files loaded to SQL endpoint and file names in blob container)
An example of one of the functions is shown here below:
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Collections.Generic;
using System.Text;
namespace AFA_ArchiveBlob
{
public static class HttpTrigger_BlobInput
{
[FunctionName("HttpTrigger_BlobInput")]
public static async Task<HttpResponseMessage> Run(
//public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "{name}")] HttpRequest req,
string name,
ILogger log,
[Blob("{name}/blobname",FileAccess.ReadWrite,Connection = "AzureWebJobsStorage")] CloudBlobContainer myCloudBlobContainer
)
{
//Execution Logged.
log.LogInformation($"HttpTrigger_BlobInput - C# HTTP trigger function processed a request.");
//Run the query against the blob to list the contents.
BlobContinuationToken continuationToken = null;
List<IListBlobItem> results = new List<IListBlobItem>();
do
{
var response = await myCloudBlobContainer.ListBlobsSegmentedAsync(continuationToken);
continuationToken = response.ContinuationToken;
results.AddRange(response.Results);
}
while (continuationToken != null);
//Query the names of the blobs. Todo: can this be a single line linq query select instead?
List<string> listBlobNames = new List<string>();
foreach (CloudBlockBlob b in results)
{
listBlobNames.Add(b.Name);
}
//Serialize the list of blob names to json for passing to function caller via return statement
var jsonReturn = JsonConvert.SerializeObject(listBlobNames);
log.LogInformation("Returning the following JSON");
log.LogInformation(jsonReturn);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(jsonReturn, Encoding.UTF8, "application/json")
};
}
}
}
Firstly, even though using keys might be convenient, I see that official documentation advises against using keys to secure function endpoint in production scenarios.
I suggest it would be a better choice to go with Azure Active Directory for security.. as explained here Secure an HTTP endpoint in production
How to Implement
I see two possible approaches:
1. Simple Approach: Check that calling application is your Azure logic app specifically
Enable Azure Active Directory Authentication for your Azure Function App. You can simply use Express settings (with create a new Azure AD app)
Enable Managed Service Identity for your Logic App.
Find out appid for Managed Service Identity associated with your logic app.. go to Azure Portal > Azure Active Directory > Enterprise Applications > All Applications > Relevant Service Principal (Explained in more detail with screenshots in another SO post here)
Authenticate your logic app to Azure function using Managed Service Identity as explained here.. Authenticate with managed identity in logic app.. note that resource being accessed will be your Azure function.
In your function code, now you can check that appid claim in access token should exactly match the appid for logic app (i.e. logic app is the one calling your function).. otherwise you can reject the call with Unauthorized exception.
2. A more declarative Approach: Have an application permission defined for Azure function app and check for this permission/role being present in auth token from client calling your function
This approach is a little more declarative, as you define an application permission that needs to be assigned to any application that can call your Azure function.
Enable Azure Active Directory Authentication for your Azure Function App. You can simply use Express settings (with create a new Azure AD app)
Now go to Azure Active Directory > App Registrations > App registration for your function app > Manifest
Add a new application role.. using json like this:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Can invoke my function",
"id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
"isEnabled": true,
"description": "Apps that have this role have the ability to invoke my Azure function",
"value": "MyFunctionValidClient"
}]
Enable Managed Service Identity for your Logic App.
Find out appid for Managed Service Identity associated with your logic app.. as already explained in approach 1 above
Assign the app permission to this managed service identity..
New-AzureADServiceAppRoleAssignment -ObjectId <logicappmsi.ObjectId> -PrincipalId <logicappmsi.ObjectId> -Id "fc803414-3c61-4ebc-a5e5-cd1675c14bbb" -ResourceId <yourfunctionaadapp.ObjectId>
Authenticate your logic app to Azure function using Managed Service Identity.. as already explained in approach 1 above
Now, in the auth token received by your function, you can check that the role claims collection must contain a role named "MyFunctionValidClient" otherwise you can reject the call with Unauthorized exception.
In addition to the above steps explained by #Rohit Below step is important:
Go to Host.json of the function.
Default authLevel : "function" should be changed to "authLevel": "anonymous".
This does not mean anyone can access the function as with Log on AD sign-in authentication required sign user however with managed identity in logic app function authenticate with service principle.

Azure Functions v2 & Google Auth

I've set up my Azure Functions 2 in the manner described in this post https://blogs.msdn.microsoft.com/stuartleeks/2018/02/19/azure-functions-and-app-service-authentication/
I can call the /.auth/login/google endpoint with the Google token and get back the easy auth token.
And I can call the /.auth/me endpoint and get back my profile info.
But in my code, the Thread.CurrentPrincipal.Identity is null. I can't seem to find any solution to this. I have verified my request has the x-zumo-auth header set to the easy auth token retrieved from the login endpoint. And of course the function is set to anonymous.
I hope I'm missing something simple. Thanks for your help.
Since November 28th 2018, this feature is now in preview. This capability is only available to the Functions 2.x runtime. It is also currently only available for .NET languages.
The ClaimsPrincipal is available as part of the request context as
shown in the following example:
using System.Net;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
public static IActionResult Run(HttpRequest req, ILogger log)
{
ClaimsPrincipal identities = req.HttpContext.User;
// ...
return new OkResult();
}
Alternatively, the ClaimsPrincipal can simply be included as an
additional parameter in the function signature:
using System.Net;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
public static void Run(JObject input, ClaimsPrincipal principal, ILogger log)
{
// ...
return;
}
Reference: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook#working-with-client-identities
Found the answer but still I'm surprised there is such a lack of clarity.
https://learn.microsoft.com/en-us/azure/app-service/app-service-authentication-overview
For Azure Functions, ClaimsPrincipal.Current is not hydrated for .NET code, but you can still find the user claims in the request headers.
But there's countless examples I'm seeing where people reference that in Azure Functions as if it should be hydrated.
I am indeed finding the claims as expected in the headers and will work with that, but I feel like there's still something that I'm missing...

Azure Function 2.x - Get Current User's Claims

I have an Azure Function 2.x (Asp.net Core) and am authenticating with Azure AD. I'm trying to access the logged-in user's Claims after authentication. Previously using Azure Functions 1.x we would get the Claims using ClaimsPrincipal.Current, as seen in the code below:
using System.Net;
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
public static HttpResponseMessage Run(HttpRequestMessage req, out object document, TraceWriter log)
{
string name = ClaimsPrincipal.Current.FindFirst("name").Value;
log.Info($"name is {name}");
return req.CreateResponse(HttpStatusCode.OK, "Done");
}
Any guidance on how we access Claims in Azure Functions 2.x using .Net Core?
This feature is now supported in C# in Azure Functions 2.0. You can now add ClaimsPrincipal as a parameter to your HttpTrigger function's signature, or you can access it on the HttpRequest object via req.HttpContext.User.
Support should be coming soon to JavaScript, and eventually all languages should support this feature.
Let me just post a code example here, see where ClaimsPrincipal parameter is:
[FunctionName("MyFunctionName")]
public static HttpResponseMessage Run(
[HttpTrigger(
AuthorizationLevel.Anonymous,
"get", "post",
Route = "MyFunctionName")]HttpRequestMessage req,
ILogger log,
ClaimsPrincipal claimsPrincipal)
{
// My function code here...
}

Resources