Base solution for your next web application
Open Closed

Add Custom Header and handle it Custom AppServiceBase #10883


User avatar
0
apti created

Please answer the following questions before submitting an issue. YOU MAY DELETE THE PREREQUISITES SECTION.

  • What is your product version? 10.4.0
  • What is your product type (Angular or MVC)? Angular
  • What is product framework type (.net framework or .net core)?.net core

Hello I need to give access to some api anonymous access, but not toatly anonymous, I am sending a custom hashed token to end-user's email. When user visits that link I can get that token and send it as parameter to my [AbpAllowAnonymous] api and encode the token and check if it's really my token or not and then process the other steps, everything works as ecpected.

But there are lots of api which are going to work in the same with that token and each time I should send it as parameter to api.

I am trying to write a public app service base and then all others public app serivce will inherit from this.

     /// <summary>
    /// Public App Service base
    /// </summary>
    public abstract class XXXPublicAppServiceBase : AbpServiceBase
    {
        public string Token { get; set; }

        protected XXXPublicAppServiceBase()
        {
            LocalizationSourceName = XXXConsts.LocalizationSourceName;
        }


        protected virtual void CheckErrors(IdentityResult identityResult)
        {
            identityResult.CheckErrors(LocalizationManager);
        }
    }

I have two question:

  1. How can I send that token to back-end as http header?
  2. How can I get token in back-end and pass it to XXXPublicAppServiceBase

5 Answer(s)
  • User Avatar
    0
    musa.demir created

    When user visits that link I can get that token and send it as parameter to my [AbpAllowAnonymous] api and encode the token and check if it's really my token or not and then process the other steps, everything works as ecpected.

    Can you please share that code part? How did you implement it?

  • User Avatar
    0
    apti created

    @musa.demir

    User clickes on link which is going to public page on my site, I get token from the URL and pass it to API as parametere. In the Api, I decode the hash and get required data from it.

    That is simple api which accept a string as aparameter. What I am asking is how to rewrite whole process to make it more OOP.

       public async Task<PagedResultDto<DocumentSharedObject>> GetRelatedDocuments(string token, GetAllRelatedDocumentsInput input)
            {
                //validate token
                RentalApplicationApplicantFormToken parsedToken = new RentalApplicationApplicantFormToken();
                try
                {
                    parsedToken = _aESEncryptionManager.ValidateAndParseToken(token);
                }
                catch (Exception)
                {
                    throw new UserFriendlyException(400, L("InvalidToken"));
                }
    
                //set tenant Id
                using (_unitOfWorkManager.Current.SetTenantId(parsedToken.TenantId))
                {
                      //Continue         
                }
            }
    

    Actually my question is for writing better code based on Zero logic.

    1. How can I send that token (or any data) to back-end as http header?
      • Zero use nswag so service proxy is auto generated, I am not able to add any thing to header of request.
    2. How can I get token in back-end and pass it to XXXPublicAppServiceBase.
      • In AppService I don't have access to HttpRequest
  • User Avatar
    1
    sedulen created

    hi @apti,

    I actually do exactly what you are trying ot achieve in two systems I have developed. I have a subset of API endpoints that require an additional header parameter.

    For these endpoints I used a custom TypeFilterAttribute and tie that to an Authorization Filter. That way for any Controller that has this attribute included in it's Class definition, the referenced IAuthorizationFilter class below is executed before the Controller method is reached.

        public class EdgeNodeAttribute : TypeFilterAttribute
        {
            public EdgeNodeAttribute() : base(typeof(EdgeNodeFilter))
            {
            }
        }
        public class EdgeNodeFilter : IAuthorizationFilter
        {
            private readonly IConfigurationRoot _appConfiguration;
    
            public EdgeNodeFilter(IWebHostEnvironment env)
            {
                _appConfiguration = env.GetAppConfiguration();
            }
    
            public void OnAuthorization(AuthorizationFilterContext context)
            {
                //this is a simple example of comparing the header value to an appConfiguration value.  You could call a DomainService here or do something else.  The only caveat here is that this is a HOT CODE PATH (ie - heavily trafficed, so make sure this is well performant.  Consider using the Async version of this method as well. (IAsyncAuthorizationFilter.OnAuthorizationAsync)
                string headerKey = context.HttpContext.Request.Headers["X-Extra-Header"];
                string configKey = _appConfiguration["EdgeNode:Header"];  
    
                if (string.IsNullOrWhiteSpace(headerKey) 
                    || string.IsNullOrWhiteSpace(configKey)
                    || headerKey != configKey)
                {
                    context.Result = new UnauthorizedResult();
                }
            }
        }
    
    

    Then for my Controller, I use that attribute:

        [Route("api/edge")]
        [ApiController]
        [EdgeNode]
        [DontWrapResult]
        public class SampleController : AbpController, ITransientDependency
    

    Then lastly, in order for this to work with the Swagger specification and nswag to generate the service proxies, you have to tell Swagger about your Header. To do that you use an "IOperationFilter" class. THIS is what tells Swagger about your additional parameter, and it's location in the Header vs as a POST-body or QueryString parameter value.

        public class EdgeNodeExtraHeaderFilter : IOperationFilter
        {
            public void Apply(OpenApiOperation operation, OperationFilterContext context)
            {
                var isEdgeNode = context.MethodInfo.DeclaringType.CustomAttributes.FirstOrDefault(a => a.AttributeType.Equals(typeof(EdgeNodeAttribute)));
    
                if(isEdgeNode != null)
                {
                    if (operation.Parameters == null)
                        operation.Parameters = new List<OpenApiParameter>();
    
                    operation.Parameters.Add(new OpenApiParameter
                    {
                        Name = "X-Extra-Header",
                        In = ParameterLocation.Header,
                        Schema = new OpenApiSchema()
                        {
                            Type = "string"
                        },
                        Required = true,
                        Description = ""
                    });
                }
            }
        }
    

    As you noted, the service proxies are generated by nswag & the swagger specification, so if the swagger specification doesn't know about your additional header parameter, it can't use it.

    The glue that brings it all together is inside of Startup.cs, where services.AddSwaggerGen(options => is called, this is where you add options.OperationFilter<EdgeNodeApiKeyHeaderFilter>();

    So to what you are trying to accomlish, my initial suggestion is to move away from a common base class, and instead use TypeFilterAttribute & IAuthorizationFilter to implement the behavior you are looking for just in those subset of AppServices / Controllers, and then wire up the additional header parameter into your Swagger specification through an IOperationFilter.

    I realize this is a non-trivial solution, and it involves ~3-4 classes. But once it's in place, it works great.

    I hope that helps! Feel free to ping me if you have questions about what I provided above. -Brian

  • User Avatar
    0
    apti created

    Hi @sedulen - Brian

    I really appreciate for sparing time and writing well detailed solution. I will check and try to implement that.If anything goes wrong I will write again.

    Thanks

  • User Avatar
    0
    ismcagdas created
    Support Team

    Thanks a lot @sedulen for your help :)