dark-loop / functions-authorize

An ASP.NET Core based authentication and authorization middleware for HTTP triggered Azure Functions (In-Proc and Isolated)
Apache License 2.0
40 stars 5 forks source link

Support for specifying a custom request header other than default "Authorization" to use to get bearer token. #57

Closed devrony closed 3 months ago

devrony commented 3 months ago

Hi, Just curious, is there way via the configuration to support a different request header for the authentication bearer token? I need to use "X-Custom-Authorization" for the Function App because I'm using Static Web Apps to host the front-end and apparently it replaces the Authorization header with a different token when making backend to the Function App endpoints as noted here in this Github issue:

I figured since this library was leveraging the built in [Authorize] attribute that this would be dependent the base library supporting this, but searched documentation and did not find much in the way of specifying the header name to use in the Program.cs configuration. Just articles telling me to create my own authorize attribute/handler. Is is possible to configure/overwrite the authorization handler logic which pulls the request header value to use?

This library is awesome though. I've currently rolled my own attribute a wrote a year back following some articles (like https://damienbod.com/2020/09/24/securing-azure-functions-using-azure-ad-jwt-bearer-token-authentication-for-user-access-tokens/), but I'd prefer to use your library now.

Thanks Devaron

artmasa commented 3 months ago

Hi @devrony,

Glad you are enjoying this framework. The cool thing about it is that is just wrapping what ASP.NET Core already has and making it available to Azure Functions. All you have to do is the same you would do in ASP.NET Core, tell it where the token is available:

services
    .AddFunctionsAuthentication(JwtFunctionsBearerDefaults.AuthenticationScheme)
    .AddJwtFunctionsBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = ctx =>
            {
                var token = ctx.Request.Headers.TryGetValue("X-Custom-Authorization", out var tokenValue) 
                    ? tokenValue.ToString().Replace("Bearer ", "")
                    : null;

                if (token is not null)
                {
                    ctx.Token = token;
                }

                return Task.CompletedTask;
            }
        };
    });

MicrosoftIdentityWebApi, OpenIdConnect and other schemes rely on the same flow to customize the behavior.

Hope this is helpfull :)

devrony commented 3 months ago

Ah. Awesome. Figured this was somewhere in the core authorization code there was a hook. This is great!! Testing out out this code in next few days.

Thanks again.

Devaron

devrony commented 3 months ago

FYI, this worked great. Another question though. For background, I have an app that is supporting multitenant accounts. My next step is to restrict access to my application for specific tenants because any user can simply consent to my application (which adds an Enterprise App in their tenant where they can control user access) and I want to deny access if not a tenant I've allowed. I'm thinking a "Policy" setup is good for this in the Program.cs file where I can store an array of tenant ids in my config and then during authorization check, compare the tenant id associated with token. NOTE: I'd need to have access to my custom bearer token to get the their tenant id. The authorization logic should be happening after the OnMessageReceived() event I'm assuming so shouldn't be an issue.

Any thoughts on this? Is there a better way?

Thanks again. Any guidance would be greatly appreciated.

artmasa commented 3 months ago

This is your lucky day as ASP.NET Core team thought about all these requirements! Just add your policy, which you can build it very elegantly, but this is just a quick way to build it inline:

const string tenantIdClaimType = "http://schemas.microsoft.com/identity/claims/tenantid";
var validTenants = new[] { "6817ff2d-9fae-455c-9ca4-a32e5f0b1724" }; // or from configuration

services
    .AddFunctionsAuthorization(options =>
    {
        options.AddPolicy(
            "InWhitelistedTenants",
            policy =>
            {
                policy.RequireClaim(tenantIdClaimType, validTenants);
                policy.AuthenticationSchemes.Add(JwtFunctionsBearerDefaults.AuthenticationScheme);
            });
    });

Then is just about decorating your function classes or functions based on your requirements with:

[Authorize("InWhitelistedTenants", JwtFunctionsBearerDefaults.AuthenticationScheme)]

Or if you want to make it simpler just inheret from AuthorizeAttribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class AuthorizeTenantAttribute : AuthorizeAttribute
{
    public AuthorizeTenantAttribute()
    {
        AuthenticationSchemes = JwtFunctionsBearerDefaults.AuthenticationScheme;
        Policy = "InWhitelistedTenants";
    }
}

...and start decorating your functions accordingly. The options are endless and that is the reason I decided to build this framework on what it's already out there in ASP.NET Core, to allow this level of customization for Azure Functions

devrony commented 3 months ago

Excellent. This is great. Thanks again for such quick responses. Will try this soon.

devrony commented 2 months ago

FYI, this authorize tenant policy worked great. Than you so much.