CoreWCF: AuthorizeAttribute not working as expected

I’ve set up Authorization in a CoreWCF sample application and it appears service methods fully run before the Authorize Attribute check occurs. This issue occurs with the following attributes in my sample: [Authorize(Policy = ("Read"))], [Authorize("Read")], and [Authorize].

Snippets of setup: NET 7.0 (also 6.0) CoreWCF.Http 1.3.0 CoreWCF.Primitives 1.3.0

Program.cs

 // Microsoft.Identity.Web v2.0.8-preview
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Read", policy => policy
    .RequireClaim(ClaimTypes.Role, new[] { "Service.Read" }));
});

IService.cs

[Authorize(Policy = ("Read"))]
//[Authorize("Read")]
//[Authorize]
public string GetData(int value)
{
    DoSomething();
    return string.Format("You entered: {0}", value);
}

Client code

var client = new CoreWCF.ServiceClient();
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = $"Bearer {accessToken}";

var context = new OperationContext(client.InnerChannel);
using var _ = new OperationContextScope(context);
context.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
Result = await client.GetDataAsync(1000);

Prerequisite: Pass in an invalid JWT token

Expected outcome: MessageSecurityException at the client side without GetData() executing in IService.cs. Actual outcome: MessageSecurityException at the client side after DoSomething() and return statement are executed within GetData() in IService.cs.

This behavior can be troublesome when trying to protect endpoints that manipulate persisted data. Is this the intended behavior of CoreWCF?

As a workaround, I do this to avoid the issue:

public string GetData(int value)
{
    /* Accessing the httpContext.User property seems to trigger an exception 
       of type System.ObjectDisposedException when the JWT token is invalid 
       or missing the required policy, which eventually returns the 
       `MessageSecurityException` when the method exits.
    */
    var httpContext = OperationContext.Current.IncomingMessageProperties["Microsoft.AspNetCore.Http.HttpContext"] as HttpContext;
    if (httpContext.User.Identity.IsAuthenticated == false) throw new UnauthorizedAccessException();

    return string.Format("You entered: {0}", value);
}

Note: Using a valid token at the service method executes the policy as expected, without issues.

Edit: Updated the sample code.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (2 by maintainers)

Most upvoted comments

@joecuevasjr, the solution to have a mix of authenticated and unauthenticated requests in a service is to have 2 interfaces, one which requires authentication for all the methods, and one which doesn’t. You can have a single service implement both interfaces and add 2 endpoints with 2 bindings. You configure one endpoint binding to use InheritedFromHost, and the other to use a client credential type of None. That way you can have clients connect to one endpoint for methods which require authentication, and another client connect to the unauthenticated endpoint for methods which are okay to call unauthenticated.
I think you should also be able to keep things simple for clients which will be authenticating by having the authenticated interface derive from the unauthenticated one so that unauthenticated methods can also be called from the authenticated client. E.g:

[ServiceContract]
public interface IUnauthenticated
{
    [OperationContract]
    DateTime GetTodaysDate();
}

[ServiceContract]
public interface IAuthenticated : IUnauthenticated
{
    [OperationContract]
    Customer GetCustomer(int customerId);
}

app.UseServiceModel(serviceBuilder =>
{
    serviceBuilder.AddService<Service>();
    serviceBuilder.AddServiceEndpoint<Service, IUnauthenticated>(new BasicHttpBinding
    {
        Security = new BasicHttpSecurity
        {
            Mode = BasicHttpSecurityMode.Transport,
            Transport = new HttpTransportSecurity
            {
                ClientCredentialType = HttpClientCredentialType.None
            }
        }
    }, "/Service.svc/noauth");
    serviceBuilder.AddServiceEndpoint<Service, IAuthenticated>(new BasicHttpBinding
    {
        Security = new BasicHttpSecurity
        {
            Mode = BasicHttpSecurityMode.Transport,
            Transport = new HttpTransportSecurity
            {
                ClientCredentialType = HttpClientCredentialType.InheritedFromHost
            }
        }
    }, "/Service.svc/auth");
    var serviceMetadataBehavior = app.Services.GetRequiredService<ServiceMetadataBehavior>();
    serviceMetadataBehavior.HttpsGetEnabled = true;
});

You could create an authenticated client which connects to /Service.svc/auth and can call GetTodaysDate and GetCustomer, but if you create a client which connects to /Service.svc/noauth, you will only be able to call GetTodaysDate. This is how you would achieve mixed authentication in WCF.

@g7ed6e, do you think it’s worth calling this out as a solution (with an example) in the blog post?

The problem with allowing authenticated and unauthenticated calls on a single endpoint is that an unauthenticated client can potentially cause a lot of work to be done by the service in preparation of dispatching the call before we discover that authentication is needed. The reason for this is the IOperationSelector interface is used to decide which operation is being called. We don’t know which operation is being called before this interface is used, so we wouldn’t know if there’s a requirement for the call to be authenticated. The IOperationSelector method could potentially trigger a full deserialization of the incoming message (it doesn’t by default, but it’s extensible and I’ve seen at least one real world example of that being done) which can result in a lot of memory allocation.