Authentication is not working when Function App is deployed in Azure. In local it works fine.

Mondal, Ram Prosad 15 Reputation points
2024-05-16T12:36:05.1033333+00:00

Hi

I am working on azure function app which is having http trigger functions. These functions are called from static web app developed using react, React front end uses MSAL authentication template for azure AAD authentication. In function app, I am having below code for adding authentication.

User's image

It works fine in local but when deployed to azure. it gives run time error "internal server error". Will appreciate any help please. I am new to azure function app and AAD authentication.

Regards,

Ram

Azure Functions
Azure Functions
An Azure service that provides an event-driven serverless compute platform.
5,147 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Jesse Yavuz 0 Reputation points
    2024-07-11T23:15:51.76+00:00

    Hey Mondal,

    I had exactly the same problem with .NET 6 in process worker! But I solved it with implementing my own token validation service. Since AddAuthentication/AddAuthorization service collections are from ASP.NET CORE but not Azure Functions, i would suggest you to implement your own token validation service. I assume you also use .NET 6

    {
      
        "AzureAdB2C:Instance": "https://contosob2c.b2clogin.com/",
        "AzureAdB2C:ClientId": "your_client_id",
        "AzureAdB2C:ClientSecret": "your_client_secret",
        "AzureAdB2C:Domain": "contosob2c.onmicrosoft.com",
        "AzureAdB2C:SignUpSignInPolicyId": "B2C_1_SignUpSignIn",
        "AzureAdB2C:ResetPasswordPolicyId": "B2C_1_ResetPassword",
        "AzureAdB2C:EditProfilePolicyId": "B2C_1_EditProfile",
        "AzureAdB2C:Issuer": "https://contosob2c.b2clogin.com/contosob2c.onmicrosoft.com/v2.0/",
        "AzureAdB2C:MetadataUrl": "https://contosob2c.b2clogin.com/contosob2c.onmicrosoft.com/v2.0/.well-known/openid-configuration"
      }
    }
    
    using System;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Text;
    using Microsoft.Extensions.Configuration;
    using Microsoft.IdentityModel.Tokens;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Extensions.Logging;
    using Microsoft.IdentityModel.Protocols;
    using Microsoft.IdentityModel.Protocols.OpenIdConnect;
    using System.Threading;
    using System.Threading.Tasks;
    namespace MyNamespace
    {
        public class JwtService
        {
            private readonly IConfiguration _configuration;
            private readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
            public JwtService(IConfiguration configuration)
            {
                _configuration = configuration;
                var metadataAddress = _configuration["AzureAdB2C:MetadataUrl"];
                _configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataAddress, new OpenIdConnectConfigurationRetriever());
            }
            public async Task<(bool, HttpResponseData)> AuthenticateAndAuthorizeAsync(HttpRequestData req, FunctionContext executionContext, string[] acceptedScopes)
            {
                var response = req.CreateResponse();
                if (!req.Headers.TryGetValues("Authorization", out var authHeaders) || !authHeaders.Any())
                {
                    response.StatusCode = HttpStatusCode.Unauthorized;
                    await response.WriteStringAsync("Unauthorized");
                    return (false, response);
                }
                var authHeader = authHeaders.First();
                if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
                {
                    response.StatusCode = HttpStatusCode.Unauthorized;
                    await response.WriteStringAsync("Unauthorized");
                    return (false, response);
                }
                var bearerToken = authHeader.Substring("Bearer ".Length);
                try
                {
                    var openIdConfig = await _configurationManager.GetConfigurationAsync(CancellationToken.None);
                    var validationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidIssuer = _configuration["AzureAdB2C:Issuer"],
                        ValidateAudience = true,
                        ValidAudience = _configuration["AzureAdB2C:ClientId"],
                        ValidateLifetime = true,
                        IssuerSigningKeys = openIdConfig.SigningKeys
                    };
                    var tokenHandler = new JwtSecurityTokenHandler();
                    var principal = tokenHandler.ValidateToken(bearerToken, validationParameters, out SecurityToken validatedToken);
                    var claims = principal.Claims;
                    var scopes = claims.Where(c => c.Type == "scp").Select(c => c.Value).ToList();
                    if (!scopes.Any(scope => acceptedScopes.Contains(scope)))
                    {
                        response.StatusCode = HttpStatusCode.Forbidden;
                        await response.WriteStringAsync("Forbidden");
                        return (false, response);
                    }
                }
                catch (Exception ex)
                {
                    var logger = executionContext.GetLogger("JwtService");
                    logger.LogError(ex, "Token validation failed");
                    response.StatusCode = HttpStatusCode.Unauthorized;
                    await response.WriteStringAsync("Unauthorized");
                    return (false, response);
                }
                return (true, null);
            }
        }
    }
    
    
    using System.Threading.Tasks;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
    using System.Net;
    namespace MyNamespace
    {
        public class MyFunction
        {
            private readonly JwtService _jwtService;
            private readonly ILogger<MyFunction> _logger;
            public MyFunction(JwtService jwtService, ILogger<MyFunction> logger)
            {
                _jwtService = jwtService;
                _logger = logger;
            }
            [Function("MyGetFunction")]
            public async Task<HttpResponseData> Run(
                [HttpTrigger(AuthorizationLevel.Function, "get", Route = "hello")] HttpRequestData req,
                FunctionContext executionContext)
            {
                var acceptedScopes = new[] { "read:messages", "write:messages" };
                var (isAuthenticated, response) = await _jwtService.AuthenticateAndAuthorizeAsync(req, executionContext, acceptedScopes);
                if (!isAuthenticated)
                {
                    return response;
                }
                var okResponse = req.CreateResponse(HttpStatusCode.OK);
                await okResponse.WriteStringAsync("Hello, world!");
                return okResponse;
            }
        }
    }
    
    

    and do not forget to add your service in startup function host builder

            public override void Configure(IFunctionsHostBuilder builder)
            {
                builder.Services.AddSingleton<JwtService>();
            }
    
    

    Hope this helps!

    0 comments No comments

  2. Arturo 46 Reputation points
    2024-08-27T14:52:32.0633333+00:00

    Take a look at DarkLoop.Azure.Functions.Authorization. It ensures all the required service needed by functions regarding authentication are loaded and also gives you the ability to specify fine grained policies per function using [Authorize] attribute.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.