1

I have an ASP.NET Core project that receives data from Sendgrid Webhook and provides authenticated APIs to business users (Azure AD).

Sendgrid is capable to be configured with OAuth 2.0 with client-credentials-flow as authentication to the webhook receiver. It doesn't support basic authentication. OAuth or none.

I had successfully configured Sendgrid's OAuth authentication to my app, leveraging OpenIddict, leaving other APIs unprotected for a while. Now I need to protect these other APIs using OAuth implicit flow before going live to production. And Sendgrid must authenticate itself to the webhook. I prefer not to deploy an additional microservice.

Question in short

Is it possible, and how, in ASP.NET Core to validate a JWT from different issuers? Examples are those application where you can login either using Facebook or Twitter or Google etc. (see note 1)

Now, to be perfectly clear to external audience, I'll be adding a detailed boring explanation.

My work so far

This is what I did to configure OpenIddict.

    public static IServiceCollection ConfigureOpenIddictAuthentication(this IServiceCollection services)
    {
        services.AddDbContext<OpenIddictDbContext>(ef => ef
                // Configure the context to use an in-memory store.
                // This prevents multiple cluster instances from deployment
                .UseInMemoryDatabase(nameof(OpenIddictDbContext))
                // Register the entity sets needed by OpenIddict.
                .UseOpenIddict()
            )
            .AddOpenIddict(options =>
                options.AddServer(server => server
                        .DisableAccessTokenEncryption() //Just for development
                        
                        //Development: no time to waste on certificate management today
                        .AddEphemeralEncryptionKey()
                        .AddEphemeralSigningKey()
                        .RegisterClaims(OpenIddictConstants.Claims.Role)
                        .RegisterScopes(OpenIddictConstants.Scopes.Roles)
                        .SetTokenEndpointUris("/api/v1/Auth/token")
                        .SetAuthorizationEndpointUris("/api/v1/Auth/authorize")
                        .AllowClientCredentialsFlow() //Only one supported by Sendgrid
                        .UseAspNetCore()
                        .EnableTokenEndpointPassthrough())
                    .AddCore(core => core.UseEntityFrameworkCore(ef => ef.UseDbContext<OpenIddictDbContext>()))
                    .AddValidation(validation => validation
                        .UseLocalServer(_ => { })
                        .UseAspNetCore(_ => { })
                    )
            )
            .AddHostedService<OpenIddictHostedService>()
            .AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
            ;

        return services;
    }

The above code (and the OpenIddictHostedService not shown) provide the client-credentials-flow /api/v1/Auth/token URL, all infrastructure required to verify the Bearer token provided by Sendgrid and the secret credentials buried in the environment.

I can run tests with Postman to submit Sendgrid test data, using a client credential that is hosted in the dev environment. And it works

Postman screen

Adding MSAL back end

I have then switched off OpenIddict for a while in my code to perform new coding. I have configured an MS AAD application registration with OpenID Connect and OAuth's implicit flow (required by Angular and Swagger). By adding the following code, and the appropriate [Authorize] attribute, I can protect the rest of my APIs with code:

        services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(Configuration)
        ;

The configuration appSettings.json contains tenant, app ID and OIDC metadata URL. There is another section, related to Swagger, that configures Swagger for OIDC using MS Azure metadata

        services.AddSwaggerGen(swagger =>
        {
            swagger.SwaggerDoc("v1", new OpenApiInfo { Title = "...", Version = "v1" });
            swagger.OperationFilter<AssignOAuth2SecurityRequirements>();
            swagger.AddSecurityDefinition("AzureAD", new OpenApiSecurityScheme
            {
                Type = SecuritySchemeType.OpenIdConnect,
                OpenIdConnectUrl = new Uri("https://login.microsoftonline.com/......./v2.0/.well-known/openid-configuration"),
            });


            swagger.AddSecurityRequirement(AssignOAuth2SecurityRequirements.APISECURITY);
        });

    public static readonly OpenApiSecurityRequirement APISECURITY = new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "oauth2"
                }
            },
            new[] { "AzureAD" }
        }
    };

The result of the above fragments is that Swagger now allows me to invoke other protected APIs of my project using the JWT issued by MS AAD

Swagger Authentication

And it damn works. But now Sendgrid's OAuth authentication is shut off.

Merging the two

Now I need that every API protected by [Authorize] check for any of the JWT tokens provided in the header (note 3: I'll use scopes on the next iteration of coding to distinguish) to authenticate the request, whether it comes from Sendgrid/Postman or Swagger/Angular.

I tried to uncomment all my code

        services.ConfigureOpenIddictAuthentication();

        services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(Configuration);

But it miserably fails at authentication using OpenIddict authorization server. Ie. it only checks for the MSAL token and rejects the whole request. Clearly, the authorization service registered later overrides the earlier.

Note 1

Actually, from a design point of view, in order to implement authentication using multiple providers one should implement an endpoint to exchange a token from the external provider for a token issued by the local authority. But Microsoft AAD uses JWTs issued from Microsoft Azure itself as bearers. Why not use them?

usr-local-ΕΨΗΕΛΩΝ
  • 26,101
  • 30
  • 154
  • 305

1 Answers1

0

I probably found a decent workaround. Thanks to the answer to Use multiple JWT Bearer Authentication https://stackoverflow.com/a/49706390/471213

What I have done was to promote MSAL to JWT Bearer Default authentication, so that every API is by default authenticated with AAD token.

Instead, on the Sendgrid Webhook API, I used [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)] to explicitly tell ASP .NET Core that I want to use that OpenIddict scheme for that API only.

Result:

        services.ConfigureOpenIddictAuthentication();

        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApi(Configuration)

And the ConfigureOpenIddictAuthentication() undergoes a change itself

        .AddHostedService<OpenIddictHostedService>()
        .AddAuthentication() // Probably not necessary, at least DO NOT add default scheme here
        ;
usr-local-ΕΨΗΕΛΩΝ
  • 26,101
  • 30
  • 154
  • 305