Search code examples
c#asp.net-core-webapiasp.net-core-localization

How to localize the error message if unauthorized


It's an asp.net core webapi project, and I simply put the [Authorize] attribute to protect those apis. If user is not authorized, the api will return "401 unauthorized".

My question is how can I localize the "401 unauthorized" message if unauthorized.

For other data annotations, I can set the ErrorMessage property of the DataAnnotaion Attribute, like [Required(ErrorMessage = "xxx")]. But, there's no such property for AuthorizeAttribute.

Thanks for @Athanasios, I came up with my solution, hope it can help someone. (ErrorMessages is just a shared class).

using System;
using System.Threading.Tasks;

using YourNamespace.Messages;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;

namespace YourNamespace.Attributes
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class LocalizedAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
    {
        public string UnauthorizedErrorMessage { get; set; }
        public string ForbiddenErrorMessage { get; set; }

        public LocalizedAuthorizeAttribute()
        {
        }

        public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.User;
            var localizer = context.HttpContext.RequestServices.GetService<IStringLocalizer<ErrorMessages>>();
            var logger = context.HttpContext.RequestServices.GetService<ILogger<LocalizedAuthorizeAttribute>>();

            string url = $"{context.HttpContext.Request.Scheme}://{context.HttpContext.Request.Host}{context.HttpContext.Request.Path}{context.HttpContext.Request.QueryString}";
            if (!user.Identity.IsAuthenticated)
            {
                logger.LogInformation($"Unauthroized access to '{url}' from {context.HttpContext.Connection.RemoteIpAddress.ToString()}");

                UnauthorizedErrorMessage = UnauthorizedErrorMessage ?? "Unauthorized";
                ProblemDetails problemDetails = new ProblemDetails()
                {
                    Title = localizer[UnauthorizedErrorMessage],
                    Detail = localizer[UnauthorizedErrorMessage + "_Detail"],
                    Status = StatusCodes.Status401Unauthorized
                };
                context.Result = new ObjectResult(problemDetails)
                {
                    StatusCode = StatusCodes.Status401Unauthorized,
                    DeclaredType = problemDetails.GetType(),
                };
            }
            else
            {
                ForbiddenErrorMessage = ForbiddenErrorMessage ?? "Forbidden";
                var authorizeService = context.HttpContext.RequestServices.GetService<IAuthorizationService>();

                if (!String.IsNullOrWhiteSpace(Policy))
                {
                    AuthorizationResult result = await authorizeService.AuthorizeAsync(user, Policy);

                    if (!result.Succeeded)
                    {
                        logger.LogWarning($"Forbidden access to '{url}' from {context.HttpContext.Connection.RemoteIpAddress.ToString()}");

                        ProblemDetails problemDetails = new ProblemDetails()
                        {
                            Title = localizer[ForbiddenErrorMessage],
                            Detail = localizer[ForbiddenErrorMessage + "_Detail"],
                            Status = StatusCodes.Status403Forbidden
                        };
                        context.Result = new ObjectResult(problemDetails)
                        {
                            StatusCode = StatusCodes.Status403Forbidden,
                            DeclaredType = problemDetails.GetType(),
                        };
                    }
                }
            }
        }
    }
}

Above code will return a problem details to the client.


Solution

  • You will need to create a custom attribute like this: https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/authentication-filters

    public class AuthenticationFailureResult : IHttpActionResult
    {
        public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
        {
            ReasonPhrase = reasonPhrase;
            Request = request;
        }
    
        public string ReasonPhrase { get; private set; }
    
        public HttpRequestMessage Request { get; private set; }
    
        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            return Task.FromResult(Execute());
        }
    
        private HttpResponseMessage Execute()
       {
            HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
            response.RequestMessage = Request;
            response.ReasonPhrase = ReasonPhrase;
            return response;
       }
    }
    

    Then instead of the Authorize attribute, you can use yours.

    For a more specific example check this SO answer here

    If you check the documentation here, you'll see there is no way to do it other than creating a custom attribute.