Base solution for your next web application
Ends in:
01 DAYS
01 HRS
01 MIN
01 SEC
Open Closed

Adding Another Jwt Authentication #10209


User avatar
0
cangunaydin created

Prerequisites

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

  • What is your product version? Asp.net Zero v9.3
  • What is your product type (Angular or MVC)? Angular
  • What is product framework type (.net framework or .net core)? .net core

If issue related with ABP Framework

  • What is ABP Framework version? v5.14.0

Hello support, I am trying to implement custom jwt authentication for my mobile application. I have partially done that and it works find when i add my authorization attribute to my controllers in my web.host project. What i couldn't understand is when i add the authorize attribute to web.application appservice classes it doesn't authorize the token so it is like allowanonymous method. Is it how it is designed? or do i miss sth over here.

here is what i have changed in the project. i have added below line to my AuthorConfigurer class.

if (bool.Parse(configuration["Authentication:AssociateJwtBearer:IsEnabled"]))
            {
                authenticationBuilder.AddJwtBearer("AssociateBearer", options =>
                 {
                     options.TokenValidationParameters = new TokenValidationParameters
                     {
                         // The signing key must match!
                         ValidateIssuerSigningKey = true,
                         IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration["Authentication:AssociateJwtBearer:SecurityKey"])),

                         // Validate the JWT Issuer (iss) claim
                         ValidateIssuer = true,
                         ValidIssuer = configuration["Authentication:AssociateJwtBearer:Issuer"],

                         // Validate the JWT Audience (aud) claim
                         ValidateAudience = true,
                         ValidAudience = configuration["Authentication:AssociateJwtBearer:Audience"],

                         // Validate the token expiry
                         ValidateLifetime = true,

                         // If you want to allow a certain amount of clock drift, set that here
                         ClockSkew = TimeSpan.Zero
                     };

                     options.SecurityTokenValidators.Clear();
                     options.SecurityTokenValidators.Add(new BookAndAdAssociateJwtSecurityTokenHandler());

                     options.Events = new JwtBearerEvents
                     {
                         OnAuthenticationFailed = context =>
                         {
                             if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                             {
                                 context.Response.Headers.Add("Token-Expired", "true");
                             }
                             return Task.CompletedTask;
                         }
                     };


                 });
            }

then i have created this class under ByteBrick.BookAndAd.Web.Authentication.JwtBearer

 public class AssociateTokenAuthConfiguration
    {

        public SigningCredentials AssociateSigningCredentials { get; set; }

        public SymmetricSecurityKey AssociateSecurityKey { get; set; }

        public string AssociateIssuer { get; set; }

        public string AssociateAudience { get; set; }

        public TimeSpan AssociateAccessTokenExpiration { get; set; }

        public TimeSpan AssociateRefreshTokenExpiration { get; set; }
    }

and for validation i copy and change the JwtSecurityTokenHandler

public class BookAndAdAssociateJwtSecurityTokenHandler : ISecurityTokenValidator
    {
        private readonly JwtSecurityTokenHandler _tokenHandler;

        public BookAndAdAssociateJwtSecurityTokenHandler()
        {
            _tokenHandler = new JwtSecurityTokenHandler();
        }

        public bool CanValidateToken => true;

        public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;

        public bool CanReadToken(string securityToken)
        {
            return _tokenHandler.CanReadToken(securityToken);
        }

        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
        {
            var cacheManager = IocManager.Instance.Resolve<ICacheManager>();
            var principal = _tokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken);

            if (!HasAccessTokenType(principal))
            {
                throw new SecurityTokenException("invalid token type");
            }


            var tokenValidityKeyClaim = principal.Claims.First(c => c.Type == AppConsts.AssociateTokenValidityKey);
            if (TokenValidityKeyExistsInCache(tokenValidityKeyClaim, cacheManager))
            {
                return principal;
            }

            var associateIdentifier = principal.Claims.First(c => c.Type == AppConsts.AssociateIdentifier);


            if (!ValidateTokenValidityKey(tokenValidityKeyClaim, associateIdentifier))
            {
                throw new SecurityTokenException("invalid");
            }

            var tokenAuthConfiguration = IocManager.Instance.Resolve<AssociateTokenAuthConfiguration>();
            cacheManager
                .GetCache(AppConsts.AssociateTokenValidityKey)
                .Set(tokenValidityKeyClaim.Value, "", absoluteExpireTime: tokenAuthConfiguration.AssociateAccessTokenExpiration);

            return principal;
        }

        private bool ValidateTokenValidityKey(Claim tokenValidityKeyClaim, Claim associateIdentifier)
        {
            bool isValid;

            using (var unitOfWorkManager = IocManager.Instance.ResolveAsDisposable<IUnitOfWorkManager>())
            {
                using (var uow = unitOfWorkManager.Object.Begin())
                {
                    using (unitOfWorkManager.Object.Current.DisableFilter(AbpDataFilters.MayHaveTenant,AbpDataFilters.MustHaveTenant))
                    {
                        using (var associateTokenRepository = IocManager.Instance.ResolveAsDisposable<IRepository<AssociateToken,long>>())
                        {
                            var associateToken=associateTokenRepository.Object.GetAll().Where(o => o.AssociateEmail == associateIdentifier.Value && o.Name==tokenValidityKeyClaim.Value).FirstOrDefault();
                            if (associateToken!=null && DateTime.UtcNow < associateToken.ExpireDate)
                            {
                                isValid = true;
                            }
                            else
                            {
                                isValid = false;
                            }
                            

                            //isValid = AsyncHelper.RunSync(() => associateRepository(user, tokenValidityKeyClaim.Value));

                            uow.Complete();
                        }
                    }
                }
            }

            return isValid;
        }

        private static bool TokenValidityKeyExistsInCache(Claim tokenValidityKeyClaim, ICacheManager cacheManager)
        {
            var tokenValidityKeyInCache = cacheManager
                .GetCache(AppConsts.AssociateTokenValidityKey)
                .GetOrDefault(tokenValidityKeyClaim.Value);

            return tokenValidityKeyInCache != null;
        }

     

        private bool HasAccessTokenType(ClaimsPrincipal principal)
        {
            return principal.Claims.FirstOrDefault(x => x.Type == AppConsts.TokenType)?.Value ==
                   TokenType.AccessToken.To<int>().ToString();
        }

    }

then i have created an appservice only for my new mobile app. calling the methods of that service here is the code for it.

[Authorize(AuthenticationSchemes = "AssociateBearer")]
    public class MobileOfferAppService : BookAndAdAppServiceBase, IMobileOfferAppService
    {

        private readonly IRepository<Offer> _offerRepository;
        private readonly IRepository<ScreenTenantImage> _screenTenantImageRepository;
        private readonly AssociateSession _session;
        private readonly IAppFolders _appFolders;
        private readonly IOfferPdfHelper _offerPdfHelper;
        private readonly IRepository<OfferPdfFile> _offerPdfFileRepository;

        public MobileOfferAppService(
            IRepository<OfferPdfFile> offerPdfFileRepository,
            IOfferPdfHelper offerPdfHelper,
            IAppFolders appFolders,
            AssociateSession session,
            IRepository<ScreenTenantImage> screenTenantImageRepository,
            IRepository<Offer> offerRepository)
        {
            _offerPdfFileRepository = offerPdfFileRepository;
            _offerPdfHelper = offerPdfHelper;
            _appFolders = appFolders;
            _session = session;
            _screenTenantImageRepository = screenTenantImageRepository;
            _offerRepository = offerRepository;
        }


        public async Task<PagedResultDto<MobileProposalListDto>> GetOffers(GetOffersForMobileInput input)
        {

and of course i have a session object and stuff. but i won't give more details about it. When i try to call this method from postman or my mobile app. Even if i have Authorize attribute it doesn't do any authorization. But when i call it from my mvc controller it seems fine. Is there any workaround to achieve it. Maybe to customize abpauthorize method to support different schemes? Cause even if it is not authorized i can see that token is expired from the controller that i have implented. Here is the snapshot for it.

and here i catch the event when i debug.

Thank you for the assistance.


21 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    What you have implemented is only covers authentication part. For authorization, you can implement an interceptor registrar simialr to https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp/Authorization/AuthorizationInterceptorRegistrar.cs and register it for your custom authorize attribute.

  • User Avatar
    0
    cangunaydin created

    Hello again, i have got your point @ismcagdas, and i have created registrar like you suggested. here is the code for it.

    internal static class DoohclickAuthorizationInterceptorRegistrar
        {
            public static void Initialize(IIocManager iocManager)
            {
                iocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered;
            }
    
            private static void Kernel_ComponentRegistered(string key, IHandler handler)
            {
                if (ShouldIntercept(handler.ComponentModel.Implementation))
                {
                    handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AbpAsyncDeterminationInterceptor<DoohclickAuthorizationInterceptor>)));
                }
            }
    
            private static bool ShouldIntercept(Type type)
            {
                if (SelfOrMethodsDefinesAttribute<DoohclickAuthorizeAttribute>(type))
                {
                    return true;
                }
    
               
    
                return false;
            }
    
            private static bool SelfOrMethodsDefinesAttribute<TAttr>(Type type)
            {
                if (type.GetTypeInfo().IsDefined(typeof(TAttr), true))
                {
                    return true;
                }
    
                return type
                    .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                    .Any(m => m.IsDefined(typeof(TAttr), true));
            }
        }
    

    also i have created custom AuthorizationInterceptor like you see in the code "DoohclickAuthorizationInterceptor" here is the code for that.

    public class DoohclickAuthorizationInterceptor : AbpInterceptorBase, ITransientDependency
        {
            private readonly IDoohclickAuthorizationHelper _authorizationHelper;
    
            public DoohclickAuthorizationInterceptor(IDoohclickAuthorizationHelper authorizationHelper)
            {
                _authorizationHelper = authorizationHelper;
            }
    
            public void Intercept(IInvocation invocation)
            {
                _authorizationHelper.Authorize(invocation.MethodInvocationTarget, invocation.TargetType);
                invocation.Proceed();
            }
    
            public override void InterceptSynchronous(IInvocation invocation)
            {
                _authorizationHelper.Authorize(invocation.MethodInvocationTarget, invocation.TargetType);
                invocation.Proceed();
            }
    
            protected override async Task InternalInterceptAsynchronous(IInvocation invocation)
            {
                var proceedInfo = invocation.CaptureProceedInfo();
    
                await _authorizationHelper.AuthorizeAsync(invocation.MethodInvocationTarget, invocation.TargetType);
    
                proceedInfo.Invoke();
                var task = (Task)invocation.ReturnValue;
                await task;
            }
    
            protected override async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
            {
                var proceedInfo = invocation.CaptureProceedInfo();
    
                await _authorizationHelper.AuthorizeAsync(invocation.MethodInvocationTarget, invocation.TargetType);
    
                proceedInfo.Invoke();
                var taskResult = (Task<TResult>)invocation.ReturnValue;
                return await taskResult;
            }
        }
    

    and i have registered these classes on the core module.

            public override void PreInitialize()
            {
                //methods for preinitialize
                DoohclickAuthorizationInterceptorRegistrar.Initialize(IocManager);
              
                 //extra methods
            }
    
            public override void Initialize()
            {
                IocManager.Register(typeof(AbpAsyncDeterminationInterceptor<DoohclickAuthorizationInterceptor>), DependencyLifeStyle.Transient);
                
                IocManager.RegisterAssemblyByConvention(typeof(BookAndAdCoreModule).GetAssembly());
            }
    

    now i have added my customattribute to my appservice and here it is.

        [DoohclickAuthorize]
        public class MobileOfferAppService : BookAndAdAppServiceBase, IMobileOfferAppService
        {
    

    now i am expecting this to work. Actually it registers DoohclickAuthorizationInterceptor and when i call my appservice method it actually calls the constructor of "DoohclickAuthorizationInterceptor" but it never triggers any of the intercept methods. Do i miss sth over here? So when i call my method it acts like anonymous. any idea what i am doing wrong?

    Ps: Also i try to implement the solution over here in this abp doc link. It didn't work either, same behavior. https://aspnetboilerplate.com/Pages/Documents/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html

    By the way i can fix my problem by replacing iauthorizationhelper with my custom authorization helper. but i didn't like that solution since i don't want a mix abpauthorization with my authorization.

    Configuration.ReplaceService<IAuthorizationHelper, DoohclickAuthorizationHelper>(DependencyLifeStyle.Transient);

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @cangunaydin

    Could you also share the signature of one appservice method which I can reproduce this problem ?

    Thanks,

  • User Avatar
    0
    cangunaydin created

    you mean the interface of ApplicationService?

    you can check the GetOffers() method

     public interface IMobileOfferAppService : IApplicationService
        {
            Task<GetOffersForMobileOutput> GetOffers(GetOffersForMobileInput input);
    
            Task<GetOfferPictureOutput> GetOfferPicture(int? screenTenantId);
    
            Task<FileDto> GetOfferPdfForAssociate(GetOfferPdfForAssociateInput input);
    
            Task CreateOrUpdateFeedback(CreateOrEditAssociateFeedbackDto input);
    
            Task<ListResultDto<AssociateFeedbackDto>> GetFeedbacks(GetFeedbacksInput input);
        }
    
        [DoohclickAuthorize]
        public class MobileOfferAppService : BookAndAdAppServiceBase, IMobileOfferAppService
        {
    
            private readonly IRepository<Offer> _offerRepository;
            private readonly IRepository<ScreenTenantImage> _screenTenantImageRepository;
            private readonly AssociateSession _session;
            private readonly IAppFolders _appFolders;
            private readonly IOfferPdfHelper _offerPdfHelper;
            private readonly IRepository<OfferPdfFile> _offerPdfFileRepository;
            private readonly IRepository<AssociateFeedback> _associateFeedbackRepository;
            private readonly IPermissionChecker _permissionChecker;
    
            public MobileOfferAppService(
                IRepository<AssociateFeedback> associateFeedbackRepository,
                IPermissionChecker permissionChecker,
                IRepository<OfferPdfFile> offerPdfFileRepository,
                IOfferPdfHelper offerPdfHelper,
                IAppFolders appFolders,
                AssociateSession session,
                IRepository<ScreenTenantImage> screenTenantImageRepository,
                IRepository<Offer> offerRepository)
            {
                _associateFeedbackRepository = associateFeedbackRepository;
                _permissionChecker = permissionChecker;
                _offerPdfFileRepository = offerPdfFileRepository;
                _offerPdfHelper = offerPdfHelper;
                _appFolders = appFolders;
                _session = session;
                _screenTenantImageRepository = screenTenantImageRepository;
                _offerRepository = offerRepository;
            }
    
            public async Task<GetOffersForMobileOutput> GetOffers(GetOffersForMobileInput input)
            {
                using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant, AbpDataFilters.MustHaveTenant, BookAndAdDbFilters.ScreenTenant))
                {
                    var associateEmailAddress = _session.AssociateEmail;
                    var commonQuery = _offerRepository.GetAll()
                                   .Where(o =>
                                                o.CopyStatus != OfferCopyStatus.Compensation &&
                                                o.Type == OfferType.Screen &&
                                                o.ContactEmail == associateEmailAddress);
    
    
    
    
                    var query = commonQuery.Where(o => input.StatusList.Contains(o.Status))
                                        .WhereIf(!input.Filter.IsNullOrWhiteSpace(),
                                        o =>
                                           (
                                            EF.Functions.Like(o.Name, $"%{input.Filter}%") ||
                                            EF.Functions.Like((string)(object)o.Id, $"%{input.Filter}%")
                                            ))
                                   .Select(o => new
                                   {
                                       TenantId = o.TenantId,
                                       Id = o.Id,
                                       CampaignDurationDays = o.CampaignDurationDays,
                                       StartDate = o.OfferItems.Where(oit => !oit.IsDeleted).SelectMany(oi => oi.Dates).Where(d => !d.IsDeleted).Min(d => d.BeginDate),
                                       EndDate = o.OfferItems.Where(oit => !oit.IsDeleted).SelectMany(oi => oi.Dates).Where(d => !d.IsDeleted).Max(d => d.EndDate),
                                       Name = o.Name,
                                       Net1 = o.Net1,
                                       Net2 = o.Net2,
                                       NetContactCount = o.NetContactCount,
                                       ProposalDueDate = o.ProposalDueDate,
                                       Status = o.Status,
                                       IsDigitallySigned=o.OfferPdfFiles.Any(x=>x.Type==OfferPdfType.Signed),
                                       Username = o.User.Name + ' ' + o.User.Surname,
                                       ScreenTenantIds = o.OfferItems.SelectMany(x => x.OfferItemScreenTenants).OrderBy((x => Guid.NewGuid())).Take(1).Select(x => x.ScreenTenantId)
                                   });
    
                    var totalNewProposalsCount = await commonQuery.Where(o => o.Status == OfferStatus.New).CountAsync();
                    var totalCustomerApprovedProposalsCount = await commonQuery.Where(o => o.Status == OfferStatus.CustomerApproved).CountAsync();
                    var totalOrdersCount = await commonQuery.Where(o => OfferManager.OrderStatuses.Contains(o.Status)).CountAsync();
    
    
    
    
                    List<MobileProposalListDto> proposals = new List<MobileProposalListDto>();
                    int totalCount = 0;
    
                    totalCount = await query.CountAsync();
    
                    var proposalsDb = await query
                        .OrderBy(input.Sorting)
                        .PageBy(input)
                        .ToListAsync();
    
                    foreach (var proposal in proposalsDb)
                    {
                        var currencyCode = await SettingManager.GetSettingValueForTenantAsync(AppSettings.TenantManagement.CurrencyCode, proposal.TenantId);
                        var currencyPriceFormat = await SettingManager.GetSettingValueForTenantAsync(AppSettings.TenantManagement.CurrencyPriceFormat, proposal.TenantId);
                        var contactCostFormat = await SettingManager.GetSettingValueForTenantAsync(AppSettings.TenantManagement.CurrencyContactCostFormat, proposal.TenantId);
                        var numberFormat = await SettingManager.GetSettingValueForTenantAsync(AppSettings.TenantManagement.NumberFormat, proposal.TenantId);
                        var percentageFormat = await SettingManager.GetSettingValueForTenantAsync(AppSettings.TenantManagement.PercentageFormat, proposal.TenantId);
                        var screenTenantId = proposal.ScreenTenantIds.FirstOrDefault();
    
                        //var screenTenantImagePath=_screenTenantImageRepository.GetAll().Where(o => o.ScreenTenantId == screenTenantId && o.IsDefault).Select(o=>o.Id).FirstOrDefault();
                        var proposalDto = new MobileProposalListDto()
                        {
                            Id = proposal.Id,
                            CampaignDurationDays = proposal.CampaignDurationDays,
                            StartDate = proposal.StartDate,
                            EndDate = proposal.EndDate,
                            Name = proposal.Name,
                            Net1 = proposal.Net1,
                            Net2 = proposal.Net2,
                            NetContactCount = proposal.NetContactCount,
                            ProposalDueDate = proposal.ProposalDueDate,
                            Status = proposal.Status,
                            Username = proposal.Username,
                            ScreenTenantId = screenTenantId,
                            ContactCostFormat = contactCostFormat,
                            CurrencyCode = currencyCode,
                            CurrencyPriceFormat = currencyPriceFormat,
                            NumberFormat = numberFormat,
                            PercentageFormat = percentageFormat,
                            TenantId = proposal.TenantId,
                            IsDigitallySigned=proposal.IsDigitallySigned
                        };
                        proposals.Add(proposalDto);
                    }
    
    
                    return new GetOffersForMobileOutput()
                    {
                        TotalNewProposalsCount = totalNewProposalsCount,
                        TotalCustomerApprovedProposalsCount = totalCustomerApprovedProposalsCount,
                        TotalOrdersCount = totalOrdersCount,
                        List = new PagedResultDto<MobileProposalListDto>(totalCount, proposals)
                    };
    
                }
            }
    
            [AbpAllowAnonymous]
            public async Task<GetOfferPictureOutput> GetOfferPicture(int? screenTenantId)
            {
                var screenTenantImagePath = await _screenTenantImageRepository.GetAll().Where(o => o.ScreenTenantId == screenTenantId && o.IsDefault).Select(o => o.Path).FirstOrDefaultAsync();
                string base64ImageRepresentation = string.Empty;
                if (!string.IsNullOrEmpty(screenTenantImagePath))
                {
                    var fullPath = Path.Combine(_appFolders.Root, screenTenantImagePath);
                    byte[] imageArray = await File.ReadAllBytesAsync(fullPath);
                    base64ImageRepresentation = Convert.ToBase64String(imageArray);
                }
                return new GetOfferPictureOutput(base64ImageRepresentation);
    
            }
    
         
    
    
            public async Task<ListResultDto<AssociateFeedbackDto>> GetFeedbacks(GetFeedbacksInput input)
            {
                var feedbacks = await _associateFeedbackRepository.GetAll().Where(o => o.OfferId == input.OfferId).ToListAsync();
                return new ListResultDto<AssociateFeedbackDto>(ObjectMapper.Map<List<AssociateFeedbackDto>>(feedbacks));
            }
    
            public async Task CreateOrUpdateFeedback(CreateOrEditAssociateFeedbackDto input)
            {
                if (input.Feedback.Id.HasValue)
                {
                    throw new UserFriendlyException("not implemented yet");
                }
                else
                {
                    await CreateFeedback(input);
                }
    
            }
    
            private async Task CreateFeedback(CreateOrEditAssociateFeedbackDto input)
            {
                var feedback = ObjectMapper.Map<AssociateFeedback>(input.Feedback);
                await _associateFeedbackRepository.InsertAsync(feedback);
            }
    
        }
    

    and here is the DoohclickAuthorize attribute. if you need it.

    /// <summary>
        /// This attribute is used on a method of an Application Service (A class that implements <see cref="IApplicationService"/>)
        /// to make that method usable only by authorized users.
        /// </summary>
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
        public class DoohclickAuthorizeAttribute:Attribute,IDoohclickAuthorizeAttribute
        {
            /// <summary>
            /// A list of permissions to authorize.
            /// </summary>
            public string[] Permissions { get; }
    
            /// <summary>
            /// If this property is set to true, all of the <see cref="Permissions"/> must be granted.
            /// If it's false, at least one of the <see cref="Permissions"/> must be granted.
            /// Default: false.
            /// </summary>
            public bool RequireAllPermissions { get; set; }
    
            /// <summary>
            /// Creates a new instance of <see cref="AbpAuthorizeAttribute"/> class.
            /// </summary>
            /// <param name="permissions">A list of permissions to authorize</param>
            public DoohclickAuthorizeAttribute(params string[] permissions)
            {
                Permissions = permissions;
            }
        }
    
  • User Avatar
    0
    cangunaydin created

    Hello @ismcagdas, is there any progress on this one?

  • User Avatar
    0
    cangunaydin created

    Hello again, it is almost 2 weeks from your last reply is somebody gonna take a look at this?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @cangunaydin

    Sorry for the delay, we are currently working on it and reply in a short time.

  • User Avatar
    0
    maliming created
    Support Team

    hi

    Can you try to the code of this PR? https://github.com/aspnetboilerplate/aspnetboilerplate/pull/6114

  • User Avatar
    0
    cangunaydin created

    Hello @maliming @¡ismcagdas, Thank you for the reply. How can i try it with Abp v5.14.0? What should i do to reflect the changes? Can i just replace a new convention class for my application?What are the steps to change AbpAppServiceConvention class for my project?

  • User Avatar
    0
    maliming created
    Support Team

    The ConfigureServices method of startup class.

    services.PostConfigure<MvcOptions>(mvcOptions =>
    {
        mvcOptions.Conventions.RemoveType<AbpAppServiceConvention>();
        mvcOptions.Conventions.Add(new MyAbpAppServiceConvention(services));
    });
    

    The MyAbpAppServiceConvention: https://gist.github.com/maliming/b252b1cf23db538769e87f7434945057

  • User Avatar
    0
    cangunaydin created

    Hello thank you for the clarification, When i try this if i send the request without bearer i am getting a response like this.

    {
        "result": null,
        "targetUrl": null,
        "success": false,
        "error": {
            "code": 0,
            "message": "Your request is not valid!",
            "details": "The following errors were detected during validation.\r\n - A non-empty request body is required.\r\n",
            "validationErrors": [
                {
                    "message": "A non-empty request body is required.",
                    "members": [
                        ""
                    ]
                }
            ]
        },
        "unAuthorizedRequest": false,
        "__abp": true
    }
    

    but i think what i should get is unAuthorizedRequest:true in my response. And i am expecting to see the interception from my DoohclickAuthorizationInterceptor when i debug but it doesn't goes to the breakpoint over there. I really wonder what i am doing wrong. By the way even if i put AbpAllowAnonymous syntax on the top of my appservice, it gives me the same error. I am trying to call this url when the app is running without token.

    https://localhost:44301/api/services/app/MobileOffer/GetOffers?StatusList=1

    and here is the input dto for the "GetOffers" method.

    public class GetOffersForMobileInput : PagedAndSortedInputDto, IShouldNormalize
        {
            public string Filter { get; set; }
            public List<OfferStatus> StatusList { get; set; }
            public void Normalize()
            {
                if (string.IsNullOrEmpty(Sorting))
                {
                    Sorting = "id DESC";
                }
            }
        }
    
  • User Avatar
    0
    maliming created
    Support Team

    hi cangunaydin

    I don't think you need to create additional interceptors and attributes. You can continue to use abpauthorize, you need to try to authenticate the second jwt in startup.

    app.UseAuthentication();
    app.UseJwtTokenMiddleware();
    app.UseJwtTokenMiddleware("AssociateBearer");
    
  • User Avatar
    0
    cangunaydin created

    Hello @maliming, I think there is a misunderstanding here or sth i didn't understand. I already implemented another authentication middleware with associatebearer. But that authentication logic is different than abp authentication so instead of using AbpSession with this line of code.

     if (!AbpSession.UserId.HasValue)
                {
                    throw new AbpAuthorizationException(
                        LocalizationManager.GetString(AbpConsts.LocalizationSourceName, "CurrentUserDidNotLoginToTheApplication")
                        );
     }
    

    I have created another session object, and created my own AuthorizationHelper, So what i want to do is i want to give some application services different authorization logic which is [DoohclickAuthorize] attribute.

      public async Task AuthorizeAsync(IEnumerable<IDoohclickAuthorizeAttribute> authorizeAttributes)
            {
              
    
                if (string.IsNullOrEmpty(_associateSession.AssociateEmail))
                {
                    throw new AbpAuthorizationException(
                        _localizationManager.GetString(AbpConsts.LocalizationSourceName, "CurrentUserDidNotLoginToTheApplication")
                        );
                }
    
                //foreach (var authorizeAttribute in authorizeAttributes)
                //{
                //    await _permissionChecker.AuthorizeAsync(authorizeAttribute.RequireAllPermissions, authorizeAttribute.Permissions);
                //}
            }
    

    So if you look at my first post you can see the authentication logic is implemented over there. what i couldn't make is intercepting the request and creating an authorization with custom authorizationhelper.

    And when i replace the convention with your convention class and added AbpAllowAnonymous to appservice why it gives me "Your request is not valid!" error? This is sth else i didn't understand.

  • User Avatar
    0
    maliming created
    Support Team

    hi cangunaydin

    Can you share a project and steps to reproduce the problem? [email protected]

    You can copy your code to the demo project.

  • User Avatar
    0
    cangunaydin created

    Hello again, I have created demo project and add the necessary changes onto it and send it your email address. hope it helps.

  • User Avatar
    0
    maliming created
    Support Team

    hi

    I got your project, Please share some necessary steps, Thanks.

  • User Avatar
    0
    cangunaydin created

    Hello, here are the steps.

    1) to reproduce the problem you can do a get call to https://localhost:44301/api/services/app/MobileOffer/GetOffers

    Expected value is unAuthorizedRequest:true in json response since you are not passing any token in the header

    What i get is

    { "result": null, "targetUrl": null, "success": false, "error": { "code": 0, "message": "Your request is not valid!", "details": "The following errors were detected during validation.\r\n - A non-empty request body is required.\r\n", "validationErrors": [ { "message": "A non-empty request body is required.", "members": [ "" ] } ] }, "unAuthorizedRequest": false, "__abp": true }

    Also I am expecting the [DoohclickAuthorize] attribute should intercept the call and check for AssociateSession, this does not happen either. 2) You can uncomment the convention config from startup.cs in web.host module services.PostConfigure<MvcOptions>(mvcOptions => { mvcOptions.Conventions.RemoveType<AbpAppServiceConvention>(); mvcOptions.Conventions.Add(new DoohclickAppServiceConvention(services)); }); if you do that and do a get call to https://localhost:44301/api/services/app/MobileOffer/GetOffers

    then it authorizes the request and returns the method response.

    Expected behavior is [DoohclickAuthorize] attribute should intercept the call and check for AssociateSession which will be null and do not authorize the request

  • User Avatar
    0
    maliming created
    Support Team

    hi

    Please make the GetOffers method virtual. in this way the Interceptor will works.

    public virtual async Task<GetOffersForMobileOutput> GetOffers(GetOffersForMobileInput input)
    

    unAuthorizedRequest will be true when AbpAuthorizationException happened. this is by design,

  • User Avatar
    0
    cangunaydin created

    Thank you for the reply, yes it works when i make the method virtual but can you explain why the method should be virtual for it to work? I wasn't expecting virtual, cause for AbpAuthorize we do not need to make it virtual. Can you elaborate on that?

  • User Avatar
    0
    maliming created
    Support Team

    hi

    can you explain why the method should be virtual for it to work?

    Castle Windsor's interceptor can only intercept:

    1. All public or public virtual methods for classes that are used over an interface (Like an application service used over a service interface).
    2. All public virtual methods for self-injected classes (Like MVC Controllers and Web API Controllers).
    3. All protected virtual methods.

    Because application services will use classes as MVC controllers, virtual methods are needed.

    I wasn't expecting virtual, cause for AbpAuthorize we do not need to make it virtual. Can you elaborate on that?

    Abp also uses MVC filters to intercept method calls. It does not require virtual methods. It is only suitable for controllers or Pages.

    https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.AspNetCore/AspNetCore/Mvc/Authorization/AbpAuthorizationFilter.cs#L54

  • User Avatar
    0
    cangunaydin created

    Now every thing is crystal clear, thank you super much. So what i did at the end is to create custom filter like AbpAuthorizationFilter and add it in startup.cs, and now i don't need to define my methods in appservice as virtual, and i can use my attribute on other methods also. I always thought that interception is always handled by Castle.Windsor. I can close this incident now. Thank you again.