14
votes

I've derived OAuthAuthorizationServerProvider in order to validate both clients and resource owners.

When I validate resource owners I find their credentials aren't valid, I call context.Rejected(), and HTTP response comes with HTTP/400 Bad Request status code while I would expect HTTP/401 Unauthorized.

How can I customize OAuthAuthorizationServerProvider's response HTTP status codes?

2

2 Answers

16
votes

This is how we override the OwinMiddleware...first we created our own middleware on top of Owin...I think we had similar issue as you did.

First need to create a constant:

public class Constants
{
    public const string OwinChallengeFlag = "X-Challenge";
}

And we override the OwinMiddleware

public class AuthenticationMiddleware : OwinMiddleware
{
    public AuthenticationMiddleware(OwinMiddleware next) : base(next) { }

    public override async Task Invoke(IOwinContext context)
    {
        await Next.Invoke(context);

        if (context.Response.StatusCode == 400 && context.Response.Headers.ContainsKey(Constants.OwinChallengeFlag))
        {
            var headerValues = context.Response.Headers.GetValues(Constants.OwinChallengeFlag);
            context.Response.StatusCode = Convert.ToInt16(headerValues.FirstOrDefault());
            context.Response.Headers.Remove(Constants.OwinChallengeFlag);
        }

    }
}

In the startup.Auth file, we allowed the overrid of the Invoke Owin Commands

public void ConfigureAuth(IAppBuilder app)
    ....
        app.Use<AuthenticationMiddleware>(); //Allows override of Invoke OWIN commands
    ....

    }

And in the ApplicationOAuthProvider, we modified the GrantResourceOwnerCredentials.

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        using (UserManager<IdentityUser> userManager = _userManagerFactory())
        {
            IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);

            if (user == null)
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                context.Response.Headers.Add(Constants.OwinChallengeFlag, new[] { ((int)HttpStatusCode.Unauthorized).ToString() }); //Little trick to get this to throw 401, refer to AuthenticationMiddleware for more
                //return;
            }
            ....
0
votes

I follow a slightly different approach using System.Security.Authentication.AuthenticationException and an exception middleware.

The AuthenticationServerProvider:

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        using (UserManager<IdentityUser> userManager = _userManagerFactory())
        {
            IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);

            if (user == null)
            {
                throw new AuthenticationException("The user name or password is incorrect.");
            }
            ....

The middleware:

public class ExceptionMiddleware : OwinMiddleware
    {
        public ExceptionMiddleware(OwinMiddleware next) : base(next)
        {
        }

        public override async Task Invoke(IOwinContext context)
        {
            try
            {
                await Next.Invoke(context);
            }
            catch (Exception ex)
            {
                HandleException(ex, context);
            }
        }

        private void HandleException(Exception ex, IOwinContext owinContext)
        {
            var errorDetails = new ErrorDetails()
            {
                Detail = ex.Message,
                Status = (int)HttpStatusCode.InternalServerError
            };

            switch (ex)
            {
                case AuthenticationException _:
                    errorDetails.Status = (int)HttpStatusCode.Unauthorized;
                    errorDetails.Title = "invalid_grant";
                    break;
                case [..]
                case Exception _:
                    errorDetails.Title = "An unexpected error occured";
                    break;
            }

            var serializedError = errorDetails.ToString();
            Log.Error($"Returning error response: {serializedError}");
            owinContext.Response.StatusCode = errorDetails.Status;
            owinContext.Response.ContentType = "application/json";
            owinContext.Response.Write(serializedError);
        }

        private class ErrorDetails
        {
            public int Status { get; set; }
            public string Title { get; set; }
            public string Detail { get; set; }

            public override string ToString()
            {
                return JsonSerializer.Serialize(this);
            }
        }

The app configuration:

public void Configure(IAppBuilder app)
    ....
        app.Use<ExceptionMiddleware>();
        // Configure auth here
    ....

    }

The result:

enter image description here