I have an ASP.Net app that is handling users with a user name and password authentication. It's working fine and I want minimal disruption. I have a new requirement to make a calculation it does available to a FunctionApp. I could just copy the database connection and code over, but that seems like a maintainability problem. Can I set up a single endpoint on that same ASP.NET app to allow a FunctionApp to call with a Managed Identity?
Everything I have found so far is using ManagedIdentity for the entire application.
example:
/api/login
Generates a JWT for users to login with username and passsword. Anonymous access allowed
/api/Bar
accessed by users with JWT
/api/Baz
accessed by users with JWT
api/Foo
accessed by WebApp with Entra
is this possible?
Use managed identity on a single endpoint in AzureAppService
Yes, it is possible to use managed identity on a single endpoint in Azure App service.
I created Asp. Net Core web API with two endpoints api/bar
accessed by user credentials
and api/foo
accessed by Managed Identity
. Created an Azure Function that calls the api/foo
endpoint using its System Assigned Managed Identity
.
In Azure I created App registration
and Expose an api
as shown below:
and Created Azure Web App and Azure Function App and assigned System assigned Managed Identity to Function app.
In code we need below Values:
var tenantId = "<TeanantId>";
var apiAppClientId = "<ClientID>";
var expectedCallerClientId = "<ApplicationId>";
you can find ApplicationId
in Function app Enterprise Application as shown below:
API Controller Code:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
namespace ManagedIdentityApi.Controllers;
[ApiController]
[Route("api")]
public class SecureController : ControllerBase
{
[HttpGet("bar")]
[Authorize]
public IActionResult Bar()
{
return Ok($"Hello {User.Identity?.Name}, you are authorized via JWT!");
}
[HttpGet("foo")]
[AllowAnonymous]
public async Task<IActionResult> Foo()
{
var authHeader = Request.Headers["Authorization"].FirstOrDefault();
if (authHeader == null || !authHeader.StartsWith("Bearer "))
return Unauthorized("Missing or invalid Authorization header.");
var token = authHeader.Substring("Bearer ".Length);
var tenantId = "<TeanantId>";
var apiAppClientId = "<ClientID>";
var expectedCallerClientId = "<ApplicationId>";
var v1Issuer = $"https://sts.windows.net/{tenantId}/";
var v2Issuer = $"https://login.microsoftonline.com/{tenantId}/v2.0";
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
$"{v2Issuer}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
var tokenHandler = new JwtSecurityTokenHandler();
try
{
var validationParams = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuers = new[] { v1Issuer, v2Issuer },
ValidateAudience = true,
ValidAudiences = new[] { $"api://{apiAppClientId}" },
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys
};
var principal = tokenHandler.ValidateToken(token, validationParams, out _);
var appid = principal.FindFirst("appid")?.Value;
if (appid != expectedCallerClientId)
return Forbid("Caller is not the expected application.");
return Ok($"MSI Token validated. Caller App ID: {appid}");
}
catch (Exception ex)
{
return Unauthorized($"Token validation failed: {ex.Message}");
}
}
}
Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "yourapp",
ValidateAudience = true,
ValidAudience = "yourapp",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("ThisIsAReallyLongSecureJwtKey123456789!"))
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Function.cs Code:
using System.Net.Http.Headers;
using Azure.Identity;
using Azure.Core;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
public class CallApiFunction
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
public CallApiFunction(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory)
{
_httpClient = httpClientFactory.CreateClient();
_logger = loggerFactory.CreateLogger<CallApiFunction>();
}
[Function("CallApiFunction")]
public async Task RunAsync([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo)
{
var credential = new DefaultAzureCredential();
var apiClientId = "api://<ClientID>";
TokenRequestContext requestContext = new(new[] { $"{apiClientId}/.default" });
AccessToken accessToken = await credential.GetTokenAsync(requestContext);
var request = new HttpRequestMessage(HttpMethod.Get, "https://<AzureWebName>.canadacentral-01.azurewebsites.net/api/foo");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Token);
var response = await _httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
_logger.LogInformation($"API Response: {response.StatusCode} - {content}");
}
}
Function Program.cs:
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = FunctionsApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.ConfigureFunctionsWebApplication();
builder.Build().Run();