401 UnAuthorized with custom response

Hello @mythz,

Hope you would be doing great. Below is my PingService. Things are working fine so far. However when the JWT token expires ServiceStack framework throws 401 UnAuthenticated Http Status code. However with this I also need to throw some additional Json response.

I have tried to use GlobalResponseFilters in App.Host.cs file to catch the response and try to customize it, but seems the request is getting completed way before it reaches to GlobalResponseFilter. Please show me a way to respond back with some JSON response along with 401 Http Status Code.

[Authenticate]
    public class Ping:Service
    {
        [LogRequestFilter]
        public object Get(PingRequest request)
        {
            var pingRes = new PingResponse();
            pingRes = DateTime.Now;     
            return pingRes;             
        }
    }

Thank you so much

The [Authenticate] attribute here is controlling the request filter response, but it does check HandleFailedAuth which flows down to ServiceStackHost HandleShortCircuitedErrors for use of ServiceExceptionHanlders (and Async) for custom results to add to the response. So you can configure your AppHost with the following to control your own custom body.

ServiceExceptionHandlers.Add((httpReq, request, exception) =>
{
    // Use pattern matching to check for a HttpError with specific status code
    if (exception is HttpError { Status: 401 })
    {
        // Return a custom response
        return new { MyCustomMessage = "You are not allowed!" };
    }
    return null;
});

This will then be used in the body and serialized along with the 401 response.

Alternatively you could override HandleShortCircuitedErrors on your AppHost if you need even more control.

1 Like

Hi @layoric ,

Appreciate your response. I have managed to implement my scenario as per your suggestion. I have added this piece of code in the AppHost.cs file. I am using res.WriteAsync method to return the response. Is this right way otherwise suggest any better way to implement this.

 public override Task HandleShortCircuitedErrors(IRequest req, IResponse res, object requestDto)
        {

            
            if(res.StatusCode==401)
            {
                var statusCode = new StatusCode();
                statusCode.ErrorCodes.Add(401);
                var status = statusCode.ToJson();
                res.WriteAsync(status);
            }
            return base.HandleShortCircuitedErrors(req, res, requestDto);
        }

Thanks

Hi @kshahzad , if that works for you that looks right. You might want to test it in combination with a custom ServiceException as your base.HandleShortCircuitedErrors will still use results from OnServiceException added to the response as well.

Hi @layoric ,

After going through the error Handling docs and some posts I am trying to learn things up and have compiled the custom class below. May I kindly request you to guide me first is this the correct way I am creating a Custom ServiceException.

public class Custom401Exception : Exception, IResponseStatusConvertible, IHasStatusCode
    {
        public Custom401Exception(string message) : base(message) { }
        public int StatusCode
        {
            get { return (int)HttpStatusCode.Unauthorized; }
        }

        public ResponseStatus ToResponseStatus()
        {
            return new ResponseStatus
            {
                ErrorCode = GetType().Name,
                Message = this.Message,
                Errors = new List<ResponseError> {
                    new ResponseError
                    {
                         ErrorCode = "CustomErrorCode",
                         Message = "This is custom exception",
                         FieldName = "FieldName",
                    }
                }
            };
        }
    }

Secondly, in your above response you have advised that I should ***

> test it with the combination of Custom ServiceException as your base.HandleShortCircuitedErrors will still use results from OnServiceException added to the response as well

***, really I am not able to understand would it be possible for you to guide bit more and if possible with some code snipet that how can I do such testing ?
|
Thanks

Hi @kshahzad ,

I’m not 100% across the requirements of the consuming client, or the context of your service application, so I can only comment on the fact of returning JSON, but this code you have provided should populate the standard ResponseStatus as other built in functionality. However, here is some feedback on the code you have provided.

  • I’m not sure about the use of ErrorCode = GetType().Name, if you want to populate it with the name of the exception type you have, it would be more efficient to use a compile time constant via typeof(Custom401Exception).Name or nameof(Custom401Exception), unless you are intending to further extend additional exception types from this class.
  • You will also need to ensure that your Response DTOs have the ResponseStatus property like so.
public class HelloResponse
{
    public string Result { get; set; }
    public ResponseStatus ResponseStatus { get; set; }
}

I was pointing out that since you are both overriding the HandleShortCircuitedErrors and calling the base.HandleShortCircuitedErrors method, you should confirm that this meets your requirements if used together with your own custom ServiceExceptionHandlers like I had shown above.

I said this since this would mean you have two locations of code that try to manually set the response. So I tested this for you with the following code:

// Configure.AppHost.cs

// Configure method..
ServiceExceptionHandlers.Add((req, request, exception) =>
{
    // Use pattern matching to check for a HttpError with specific status code
    if (exception is HttpError { Status: 401 })
    {
        // Return a custom response
        return new { MyCustomMessage = "You are not allowed!" };
    }
    return null;
});

public override Task HandleShortCircuitedErrors(IRequest req, IResponse res, object requestDto)
{
    if(res.StatusCode==401)
    {
        var statusCode = new { ErrorCodes = new List<int>() };
        statusCode.ErrorCodes.Add(401);
        var status = statusCode.ToJson();
        res.WriteAsync(status);
    }
    return base.HandleShortCircuitedErrors(req, res, requestDto);
}

This causes the following issue:

Error executing async afterHeaders: Response Content-Length mismatch: too many bytes written (62 of 20).

So you would likely want to either not call the base method, missing out on the use of ServiceExceptionHandlers and/or handle this yourself so that these two pieces of custom functionality don’t conflict.

If you are willing to share more about the specific goals/requirements I might be able to help more, but with what you’ve specified so far, there are various options you could go with to achieve a 401 UnAuthorized with custom response as you originally stated.

If you want to go with the custom exception option, I’d suggest having a look at the ValidationException code to get a good idea of how that works, as it might help you with the behavior you are aiming to achieve.

Hope that helps.

Hi @layoric,

Apologize couldn’t respond on time as I am on holidays. Using my limited knowledge I will try to follow your suggestion and update you.

Really appreciate your kind support.

Regards

Hi @layoric,

Let me try to explain what I am looking for hoping I will succeed and you will able to guide me to the best possible direction. By the way your above response helped me out to achieve another mile but I need expert opinion.

Background
When a user authenticates using the username and password credentials upon successful authentication he receives bearerToken and refreshToken as shown below. Until the token is valid and not expired user is able to make API calls.

Successful Authenticated Response

{
    "userId": "2",
    "sessionId": "2XopRT7...",
    "userName": "user1",
    "displayName": "User 1",
    "bearerToken": "eyJ0eXAiOiJKV1QiLC...",
    "refreshToken": "eyJ0eXAiOiJKV1RSIiw...",
    "profileUrl": "data:imag",
    "roles": [],
    "permissions": []
}

Requirements
I am trying to standardize the response. In these 2 cases

  1. When the token is expired

  2. When the user is trying to use a token which is not valid.

Token Expired Case:
When the token is expired ServiceStack is throwing this response

{
    "responseStatus": {
        "errorCode": "TokenException",
        "message": "Token has expired",
        "stackTrace": "ServiceStack.TokenException: Token has expired\r\n   at ServiceStack.Auth.JwtAuthProvi........",
        "errors": []
    }
}

This is the desired response I want to send to user along with the Http Status Code 401 what ServiceStack is already throwing

{
    "errorCode": "401",
    "message": "Token Expired"    
}

Invalid Token Case

When the user is using invalid token there is no response body being thrown by ServiceStack. Only 401 Unauthorized Http Status Code.

Like Case 1, I want to control the response and want to throw the standard response like this

{
    "errorCode": "401",
    "message": "Invalid Token"    
}

I hope I didn’t give you headache by writing such a long text. Apologize if my question is very basic as due to limited knowledge I am trying to progress.

Regards,

Hi @kshahzad ,

I think using the ServiceExceptionHanlders here to detect the TokenException and related details should be enough to return your custom response.

Saying that, If the user has a refreshToken, the user can attempt to get another token. Only if the refreshToken fails do they need to be prompted to re-authenticate with another method. The ServiceStack Service Clients do this automatically and use the JWT as a cookie to handle this. This also removes the need for the bearerToken and refreshToken to be specified in the response, since they are managed in cookies which most clients will manage as normal.

If you still want to manage the custom messages, see my example of using the ServiceExceptionHandlers as you have access to the exception where you could detect the type of TokenException and manage your custom response there.

Hope that helps.