12

We are using the OAuthAuthorizationServerProvider class to do authorization in our ASP.NET Web Api app.

If the provided username and password is invalid in GrantResourceOwnerCredentials, the call

context.SetError( "invalid_grant", "The user name or password is incorrect." );

Produces the following Json result:

{
    "error": "invalid_grant",
    "error_description": "The user name or password is incorrect."
}

Is there any way to customize this error result?
I would like to make it consistent with default error message format used in other parts of the API:

{
    "message": "Some error occurred."
}

Is this possible to achieve with the OAuthAuthorizationServerProvider?

Mark Vincze
  • 7,737
  • 8
  • 42
  • 81
  • 1
    Whilst the answers below demonstrate how to do this, I would caution against doing this, as the default response returned conforms to the OAuth 2.0 specification, whereas your modified response would not. This might be acceptable for internal-only APIs. However, if this API is publicly accessible, you probably want to be following the specification and not inventing new conventions. – Chris Nov 06 '16 at 18:16
  • I found that these answers do not work and it seems being the OAuth response to look more like a standard Web API response is not as easy as one might think. – trees_are_great Jul 24 '17 at 14:30

3 Answers3

11

This is how I did it.

string jsonString = "{\"message\": \"Some error occurred.\"}";

// This is just a work around to overcome an unknown internal bug. 
// In future releases of Owin, you may remove this.
context.SetError(new string(' ',jsonString.Length-12)); 

context.Response.StatusCode = 400;
context.Response.Write(jsonString);
Dasun
  • 3,244
  • 1
  • 29
  • 40
  • 1
    As a note I had tried creating a OwinMiddleware class to override the context.Response.Body, but that didn't work either if anyone tries. This worked perfectly. – DDiVita Sep 02 '15 at 19:54
  • 4
    This did not work for me on web api 2. The response was not proper json, because of that internal bug that you mentioned. – trees_are_great Jul 24 '17 at 14:30
7

+1 for Dasun's answer. Here is how I extended it a bit further.

public class ErrorMessage
{
    public ErrorMessage(string message)
    {
        Message = message;
    }

    public string Message { get; private set; }
}

public static class ContextHelper
{
    public static void SetCustomError(this OAuthGrantResourceOwnerCredentialsContext context, string errorMessage)
    {
        var json = new ErrorMessage(errorMessage).ToJsonString();

        context.SetError(json);
        context.Response.Write(json);
    }
}

The .ToJsonString() is another extension method that uses the Newtonsoft.Json library.

public static string ToJsonString(this object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }

Usage:

context.SetCustomError("something went wrong");
ajpetersen
  • 639
  • 8
  • 17
3

1+ again for "user2325333" and "Dasun's" answer his solution, your answers are good but still there is an issue . The Josn Tag still return {error:""}, thus I replace the context.Response.Body with empty MemoryStream and here the work example

public static class ContextHelper
{
    public static void SetCustomError(this OAuthGrantResourceOwnerCredentialsContext context,string error, string errorMessage)
    {
        var json = new ResponseMessage
        { Data = errorMessage, Message = error, IsError = true }.ToJsonString();
        context.SetError(json);
        context.Response.Write(json);
        Invoke(context);
    }
    public static string ToJsonString(this object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
    static async Task Invoke(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var owinResponseStream = new MemoryStream();
        var customResponseBody = new System.Net.Http.StringContent(JsonConvert.SerializeObject(new ResponseMessage()));
        var customResponseStream = await customResponseBody.ReadAsStreamAsync();
        await customResponseStream.CopyToAsync(owinResponseStream);
        context.Response.ContentType = "application/json";
        context.Response.ContentLength = customResponseStream.Length;
        context.Response.Body = owinResponseStream;
    }
}
public class ResponseMessage
{
    public bool IsError { get; set; }
    public string Data { get; set; }
    public string Message { get; set; }
}

for usage of this context

 public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        if (!context.Match.Passcode)
        {
            context.SetCustomError("invalid_grant", "Passcode is invalid.");
            return;
        }
    }

The Result will be as enter image description here

Ahmad Hindash
  • 1,519
  • 15
  • 16
  • Hey, thank you for the answer, it really did work. I have two questions regarding this, 1st: I tried "context.Response.StatusCode = (int)HttpStatusCode.Forbidden;" this code but Status code is still 400 Bad Request. – Fatih TAN Jul 21 '23 at 14:24
  • 2nd: What is main purpose of this Invoke, it's kind of confusing because we call invoke after write to response and we changed response body with empty object stream. How can this work? – Fatih TAN Jul 21 '23 at 14:26