Alright, fellow cloud adventurers, let’s talk about Azure Functions and the wild ride that is .NET 8 Isolated Mode. You see, when it comes to authorizing functions for specific user groups, many of us rely on the trusty Authorize-Attribute. It’s been our go-to for granting access to authenticated user groups with ease.

But hold onto your hats, because things take an unexpected turn when you try to wield this power in Azure Functions .NET 8 Isolated Mode. Suddenly, that trusty old Authorize-Attribute seems to have lost its mojo.

What gives, you ask? Well, it seems the way our functions check request headers isn’t quite the same as it used to be. But fear not, intrepid developers! With a dash of brainstorming and a sprinkle of ingenuity, I stumbled upon a solution.

Enter: the DIY token checker. That’s right, folks. When the going gets tough, the tough get coding. I rolled up my sleeves and crafted a nifty little helper to handle token checks for specific user groups.

Because in the ever-evolving world of Azure Functions and .NET 8 Isolated Mode, sometimes you’ve got to take matters into your own hands. So here’s to blazing new trails, overcoming unexpected challenges, and always finding a way to make our functions work for us – no matter what mode they’re in.

using Microsoft.Azure.Functions.Worker.Http;
using System.Security.Claims;
using System.Security.Principal;

namespace OliverS.Helper
{
    public static class ClaimsHelper
    {
        public static bool CheckPrincipalHasClaim(HttpRequestData req, string claimType, string claimValue)
        {
            ClaimsPrincipal? principal = ClaimsPrincipalHelper.ParseFromRequest(req);

            if (principal == null)
            {
                return false;
            }

            if (principal.HasClaim(claimType, claimValue))
            {
                return true;
            }
            return false;
        }

        public static bool ClaimExists(this IPrincipal principal, string claimType)
        {
            if (principal is not ClaimsPrincipal ci)
            {
                return false;
            }

            Claim? claim = ci.Claims.FirstOrDefault(x => x.Type == claimType);
            return claim != null;
        }

        public static bool HasClaim(
            this IPrincipal principal, 
            string claimType,
            string claimValue, 
            string issuer = null)
        {
            if (principal is not ClaimsPrincipal ci)
            {
                return false;
            }

            var claim = ci
                .Claims
                .FirstOrDefault(x => x.Type == claimType && x.Value == claimValue && (issuer == null || x.Issuer == issuer));
            return claim != null;
        }

        public static string GetUserEmail(HttpRequestData req)
        {
            ClaimsPrincipal? principal = ClaimsPrincipalHelper.ParseFromRequest(req);
            if (principal == null)
            {
                return string.Empty;
            }
            string result = principal.FindFirst("unique_name")?.Value ?? string.Empty;
            return result;
        }

        public static string GetUserName(HttpRequestData req)
        {
            ClaimsPrincipal? principal = ClaimsPrincipalHelper.ParseFromRequest(req);
            if (principal == null)
            {
                return string.Empty;
            }
            string result = principal.Identity?.Name ?? string.Empty;
            return result;
        }
    }
}

Now, picture this: a trusty claims helper swoops in to save the day! With this nifty tool, we can determine whether a user possesses a specific claim they’re eager to access.

It’s like having a guardian angel for our authentication process, ensuring that only those with the right credentials can venture forth into the realm of our Azure Functions. So whether it’s a VIP pass to a restricted area or a golden ticket to exclusive features, our claims helper is here to grant access to those who truly deserve it.

[Function("mysamplefunction")]
public async Task<HttpResponseData> GetMyData(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "mydata")] HttpRequestData req)
{
    #region Is User Admin

    if (!ClaimsHelper.CheckPrincipalHasClaim(req, Const.RoleConst.RolesClaim, Const.RoleConst.Admin))
    {
        var unauthorizedResponse = new CustomResponse()
        {
            StatusCode = HttpStatusCode.Unauthorized,
            Message = "Unauthorized."
        };
        return unauthorizedResponse.CreateResponse(req);
    }

    #endregion

    
    HttpResponseData response = new CustomResponse()
    {
        StatusCode = HttpStatusCode.OK,
        Message = "Rolls found",
        Result = rolls
    }.CreateResponse(req);

    return response;
}

In this sample I’m using a helper class to have an easier work with HttpResponse.

using Microsoft.Azure.Functions.Worker.Http;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Haehl.IoTRoll.Models.Response
{
    public class CustomResponse
    {
        [JsonIgnore]
        public HttpStatusCode StatusCode { get; set; }

        public string Message { get; set; } = string.Empty;

        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public string[]? ErrorMessages { get; set; } = null;

        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public object Result { get; set; } = default!;

        public HttpResponseData CreateResponse(HttpRequestData req)
        {
            HttpResponseData response = req.CreateResponse(StatusCode);
            response.Headers.Add("Content-Type", "application/json; charset=utf-8");

            try
            {
                var json = JsonSerializer.Serialize(this);
                response.WriteStringAsync(json);
                return response;
            } 
            catch (Exception exc)
            {
                response.WriteString(exc.Message);
                return response;
            }
            
        }
    }
}

Conclusion

Check the claim of an user is simple, but not automatically available in .net core 8 isolated mode for some reasons.