7  Security Component

PlantUML Diagram

7.1 Managed Identity

The Header of any incoming message will contain 2 header keys IDENTITY_ENDPOINT and IDENTITY_HEADER they sometimes appear under there alias names MSI_ENDPOINT and MSI_HEADER.

7.1.1 Retrieve the MI token

Use the DefaultAzureCredential to obtain the token for the managed identity.

var credential = new DefaultAzureCredential();
var token = await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://management.azure.com/.default" }));

7.1.2 Validate the token

Use the Microsoft.IdentityModel.Tokens library to validate the token. This involves checking the token’s signature, issuer, audience, and expiration.

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

public bool ValidateToken(string token)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var validationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = "https://sts.windows.net/{tenant-id}/",
        ValidateAudience = true,
        ValidAudience = "https://management.azure.com/",
        ValidateLifetime = true,
        IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
        {
            // Retrieve signing keys from Azure AD
            var discoveryDocument = "https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration";
            var keys = new JsonWebKeySet(discoveryDocument).GetSigningKeys();
            return keys;
        }
    };

    try
    {
        tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
        return true;
    }
    catch
    {
        return false;
    }
}

7.1.3 Authorise the caller

Ensure the caller’s managed identity has the necessary permissions to access the function. This can be done by checking the claims in the token.

var jwtToken = tokenHandler.ReadJwtToken(token);
var claims = jwtToken.Claims;

// Check for specific roles or permissions
var hasAccess = claims.Any(c => c.Type == "roles" && c.Value == "YourRequiredRole");

7.1.4 Function Code

By combining the above snippets, you would be able to check and authorise the calling application.

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    var token = req.Headers["Authorization"].ToString().Replace("Bearer ", "");
    if (!ValidateToken(token))
    {
        return new UnauthorizedResult();
    }

    // Proceed with your function logic
    return new OkResult();
}

7.2 User Authentication

N.B. If a User token is included in the header then check the user is authorised.

Use the Microsoft.Identity.Web library to handle authentication and authorization in your function.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web;
using System.Threading.Tasks;

public static class MyFunction
{
    [FunctionName("MyFunction")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        var user = req.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            return new UnauthorizedResult();
        }

        // Check for specific roles or claims
        if (!user.IsInRole("YourRequiredRole"))
        {
            return new ForbidResult();
        }

        // Proceed with your function logic
        return new OkObjectResult("User is authorized");
    }
}