-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug] Azure function appears 'broken' in portal #916
Comments
@maliksahil : would you be able to help here? |
@smkanadl : did you read Use Microsoft identity web with Azure functions and see the maliksahil/ms-identity-azurefunctions-microsoft-identity-web code sample ? |
@jmprieur Yes, I've read his blog post. His example code and the official test code hosted here are identical (at least I've seen no difference). |
When you call AddAuthentication you override the default auth providers in Azure functions (Arm, ScriptAuthLevel, JwtBearer) which naturally causes havoc with the Azure backend. see: Azure/azure-functions-host#6805 I've been trying to use reflection to restore those providers. From early testing the code below seems to work.
public override void Configure(IFunctionsHostBuilder builder)
{
// Authentication Provider
builder.Services.AddAuthentication(sharedOptions =>
{
// Assigning the DefaultScheme also creates problems.
//sharedOptions.DefaultScheme = Microsoft.Identity.Web.Constants.Bearer;
//sharedOptions.DefaultChallengeScheme = Microsoft.Identity.Web.Constants.Bearer;
})
.AddArmToken()
.AddScriptAuthLevel()
//.AddScriptJwtBearer();
.AddMicrosoftIdentityWebApi(Configuration.GetSection(Microsoft.Identity.Web.Constants.AzureAdB2C))
.EnableTokenAcquisitionToCallDownstreamApi()
//.AddDownstreamWebApi("", Configuration.GetSection(""))
.AddInMemoryTokenCaches();
services.AddAuthorization(options =>
{
options.AddScriptPolicies();
});
services.AddAuthLevelAuthorizationHandler()
.AddNamedAuthLevelAuthorizationHandler()
.AddFunctionAuthorizationHandler();
}
public static class AuthenticationStartupExtensions
{
#if DEBUG
private const string CliAuthenticationHandler = "Azure.Functions.Cli.Actions.HostActions.WebHost.Security.CliAuthenticationHandler`1";
private static Type? _cliAuthenticationHandlerType = null;
private static Type CliAuthenticationHandlerType
{
get
{
if (_cliAuthenticationHandlerType is null)
{
_cliAuthenticationHandlerType = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.IsClass && x.FullName == CliAuthenticationHandler);
}
return _cliAuthenticationHandlerType;
}
}
#endif
// ARM Authentication
#if DEBUG
private const string ArmAuthenticationOptions = "Microsoft.Azure.WebJobs.Script.WebHost.Security.Authentication.ArmAuthenticationOptions";
private static Type? _armAuthenticationOptionsType = null;
private static Type ArmAuthenticationOptionsType
{
get
{
if (_armAuthenticationOptionsType is null)
{
_armAuthenticationOptionsType = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.IsClass && x.FullName == ArmAuthenticationOptions);
}
return _armAuthenticationOptionsType;
}
}
public static AuthenticationBuilder AddArmToken(this AuthenticationBuilder builder)
{
var method = builder.GetType().GetMethods()
.Where(x => x.Name == nameof(AuthenticationBuilder.AddScheme))
.FirstOrDefault(x => x.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(ArmAuthenticationOptionsType, CliAuthenticationHandlerType.MakeGenericType(ArmAuthenticationOptionsType));
return genericMethod.Invoke(builder, new object[] { "ArmToken", new Action<dynamic>((_) => { }) }) as AuthenticationBuilder;
}
#else
private const string ArmAuthenticationExtensions = "Microsoft.Extensions.DependencyInjection.ArmAuthenticationExtensions";
private static Type? _armAuthenticationExtensionsTypes = null;
private static Type ArmAuthenticationExtensionsType
{
get
{
if (_armAuthenticationExtensionsTypes is null)
{
_armAuthenticationExtensionsTypes = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.IsClass && x.FullName == ArmAuthenticationExtensions);
}
return _armAuthenticationExtensionsTypes;
}
}
public static AuthenticationBuilder AddArmToken(this AuthenticationBuilder builder)
{
var method = ArmAuthenticationExtensionsType.GetMethods()
.Where(x => x.Name == nameof(AddArmToken))
.FirstOrDefault(x => x.GetParameters().Length == 1);
return method.Invoke(null, new object[] { builder }) as AuthenticationBuilder;
}
#endif
// Script Authentication Level
#if DEBUG
private const string AuthenticationLevelOptions = "Microsoft.Azure.WebJobs.Script.WebHost.Authentication.AuthenticationLevelOptions";
private static Type? _authenticationLevelOptionsType = null;
private static Type AuthenticationLevelOptionsType
{
get
{
if(_authenticationLevelOptionsType is null)
{
_authenticationLevelOptionsType = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.IsClass && x.FullName == AuthenticationLevelOptions);
}
return _authenticationLevelOptionsType;
}
}
public static AuthenticationBuilder AddScriptAuthLevel(this AuthenticationBuilder builder)
{
var method = builder.GetType().GetMethods()
.Where(x => x.Name == nameof(AuthenticationBuilder.AddScheme))
.FirstOrDefault(x => x.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(AuthenticationLevelOptionsType, CliAuthenticationHandlerType.MakeGenericType(AuthenticationLevelOptionsType));
return genericMethod.Invoke(builder, new object[] { "WebJobsAuthLevel", new Action<dynamic>((_) => { }) }) as AuthenticationBuilder;
}
#else
private const string AuthLevelExtensionsExtensions = "Microsoft.Extensions.DependencyInjection.AuthLevelExtensions";
private static Type? _authLevelExtensionsExtensionsTypes = null;
private static Type AuthLevelExtensionsExtensionsType
{
get
{
if (_authLevelExtensionsExtensionsTypes is null)
{
_authLevelExtensionsExtensionsTypes = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.IsClass && x.FullName == AuthLevelExtensionsExtensions);
}
return _authLevelExtensionsExtensionsTypes;
}
}
public static AuthenticationBuilder AddScriptAuthLevel(this AuthenticationBuilder builder)
{
var method = AuthLevelExtensionsExtensionsType.GetMethods()
.Where(x => x.Name == nameof(AddScriptAuthLevel))
.FirstOrDefault(x => x.GetParameters().Length == 1);
return method.Invoke(null, new object[] { builder }) as AuthenticationBuilder;
}
#endif
// JWT Bearer
private const string JwtBearerExtensions = "Microsoft.Extensions.DependencyInjection.ScriptJwtBearerExtensions";
private static Type? _jwtBearerExtensionsTypes = null;
private static Type JwtBearerExtensionsType
{
get
{
if (_jwtBearerExtensionsTypes is null)
{
_jwtBearerExtensionsTypes = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.IsClass && x.FullName == JwtBearerExtensions);
}
return _jwtBearerExtensionsTypes;
}
}
public static AuthenticationBuilder AddScriptJwtBearer(this AuthenticationBuilder builder)
{
var method = JwtBearerExtensionsType.GetMethods()
.Where(x => x.Name == nameof(AddScriptJwtBearer))
.FirstOrDefault(x => x.GetParameters().Length == 1);
return method.Invoke(null, new object[] { builder }) as AuthenticationBuilder;
}
} public static class AuthorizationStartupExtensions
{
private const string AuthorizationOptionsExtensions = "Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.Policies.AuthorizationOptionsExtensions";
private static Type? _authorizationOptionsExtensionsType = null;
private static Type AuthorizationOptionsExtensionsType
{
get
{
if (_authorizationOptionsExtensionsType is null)
{
_authorizationOptionsExtensionsType = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(t => t.GetTypes())
.FirstOrDefault(t => t.IsClass && t.FullName == AuthorizationOptionsExtensions);
}
return _authorizationOptionsExtensionsType;
}
}
public static void AddScriptPolicies(this AuthorizationOptions options)
{
var method = AuthorizationOptionsExtensionsType.GetMethods()
.Where(x => x.Name == nameof(AddScriptPolicies))
.FirstOrDefault(x => x.GetParameters().Length == 1);
method.Invoke(null, new object[] { options });
}
private const string AuthLevelAuthorizationHandler = "Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.AuthLevelAuthorizationHandler";
private static Type? _authLevelAuthorizationHandlerType = null;
private static Type AuthLevelAuthorizationHandlerType
{
get
{
if (_authLevelAuthorizationHandlerType is null)
{
_authLevelAuthorizationHandlerType = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(t => t.GetTypes())
.FirstOrDefault(t => t.IsClass && t.FullName == AuthLevelAuthorizationHandler);
}
return _authLevelAuthorizationHandlerType;
}
}
public static IServiceCollection AddAuthLevelAuthorizationHandler(this IServiceCollection services)
{
var method = typeof(ServiceCollectionServiceExtensions).GetMethods()
.Where(x => x.Name == nameof(ServiceCollectionServiceExtensions.AddSingleton))
.FirstOrDefault(x => x.GetParameters().Length == 1);
var genericMethod = method.MakeGenericMethod(typeof(IAuthorizationHandler), AuthLevelAuthorizationHandlerType);
return genericMethod.Invoke(null, new object[] { services }) as IServiceCollection;
}
private const string NamedAuthLevelAuthorizationHandler = "Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.NamedAuthLevelAuthorizationHandler";
private static Type? _namedAuthLevelAuthorizationHandlerType = null;
private static Type NamedAuthLevelAuthorizationHandlerType
{
get
{
if (_namedAuthLevelAuthorizationHandlerType is null)
{
_namedAuthLevelAuthorizationHandlerType = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(t => t.GetTypes())
.FirstOrDefault(t => t.IsClass && t.FullName == NamedAuthLevelAuthorizationHandler);
}
return _namedAuthLevelAuthorizationHandlerType;
}
}
public static IServiceCollection AddNamedAuthLevelAuthorizationHandler(this IServiceCollection services)
{
var method = typeof(ServiceCollectionServiceExtensions).GetMethods()
.Where(x => x.Name == nameof(ServiceCollectionServiceExtensions.AddSingleton))
.FirstOrDefault(x => x.GetParameters().Length == 1);
var genericMethod = method.MakeGenericMethod(typeof(IAuthorizationHandler), NamedAuthLevelAuthorizationHandlerType);
return genericMethod.Invoke(null, new object[] { services }) as IServiceCollection;
}
private const string FunctionAuthorizationHandler = "Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.FunctionAuthorizationHandler";
private static Type? _functionAuthorizationHandlerType = null;
private static Type FunctionAuthorizationHandlerType
{
get
{
if (_functionAuthorizationHandlerType is null)
{
_functionAuthorizationHandlerType = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(t => t.GetTypes())
.FirstOrDefault(t => t.IsClass && t.FullName == FunctionAuthorizationHandler);
}
return _functionAuthorizationHandlerType;
}
}
public static IServiceCollection AddFunctionAuthorizationHandler(this IServiceCollection services)
{
var method = typeof(ServiceCollectionServiceExtensions).GetMethods()
.Where(x => x.Name == nameof(ServiceCollectionServiceExtensions.AddSingleton))
.FirstOrDefault(x => x.GetParameters().Length == 1);
var genericMethod = method.MakeGenericMethod(typeof(IAuthorizationHandler), FunctionAuthorizationHandlerType);
return genericMethod.Invoke(null, new object[] { services }) as IServiceCollection;
}
} |
I have been trying to look into this, since we are hitting it too... I have hard time understanding at which point the /cc: @Valks |
@hajekj It's been two weeks since I looked at this but my recollection is when you call If you register a provider yourself and then go If you go through the code I provided above you can use reflection to re-add the functions providers when you register your custom provider restoring the original functionality along with your custom provider. I remember also having an issue when setting the default scheme, you have to leave it blank i.e. don't set it to |
@Valks Thanks for the insight! I wasn't able to check if the Function providers get registered before or after (it would make sense if they got registered before), but then, the implementation of I actually tried doing this:
and unless I missed something, I was able to authenticate with both schemes, which would support, that it doesn't get overridden (at least in this case). I suppose it all comes to the fact that there is something different going on with the Update: Also wanted to add that your solution seems to be so far the only thing which fixes the behavior. Thanks! |
@hajekj : on the second line, you should pass-in a different authentication scheme to AddAuthentication, otherwise this will map to the default authorization scheme: services.AddAuthentication().AddJwtBearer("Bearer1", ...);
services.AddAuthentication("Bearer2").AddJwtBearer("Bearer2", ...); |
@jmprieur: Yes, I am aware of that, I am just trying to understand what happens in the Function's runtime that causes the pre-registered schemes like (ArmToken, ...) to be "unregistered", therefor causing the issue described above. |
I will try to ask some related questions to this at the Functions Live event today - https://www.youtube.com/watch?v=4n0QbG0wYEI |
closing, as Azure Functions with .NET 8 is working. Please re-open if that's not the case |
Which version of Microsoft Identity Web are you using?
Note that to get help, you need to run the latest version.
Where is the issue?
An Azure function validating tokens in http triggers using the supplied example
This is a new app or an experiment.
Repro
Expected behavior
Function is up and running after deployment.
Actual behavior
func azure functionapppublish
yieldsError calling sync triggers (BadRequest)
Unable to fetch the host status of your function app. To use log streaming, please make sure your function host is running.
Azure Functions Runtime is unreachable.
AddAuthentication
setup in the startup,The text was updated successfully, but these errors were encountered: