Search code examples
c#asp.net-core-webapipaymentsaas

How should the restriction be done when the subscription period expires?


In a Rest Api SaaS project developed with .Net Core 3.1.

When the user's subscription expires (needs to pay), what kind of a method would be better to follow.

There are 2 methods that I think of but I think there will be some problems in both of them.

Method 1) Checking the subscription status during JWT generate and not generating JWT if the subscription period has expired:

If I use this method;

Advantage: Since a token is not given to a user whose subscription expires, they will not be able to access other endpoints. I think this will work extremely safe without doing any other coding work.

Disadvantage: When I need to redirect the user to the payment page, I will have to do a special work for the payment endpoints since there are no tokens.(Example: Password Reset Methods) I will get it with query string, I think I can create a special token for this method. But I think there might be a security bug because I couldn't protect this process with my standard authorization method?

Method 2) Even if the subscription expires, jwt will be generated, but membership will be restricted:

If I use this method;

Advantage: I can use my standard authorization method without any problems when I need to direct the user to the payment endpoints or to another endpoints. I will use with jwt and security bugs will be considerably reduced.

Disadvantage: I need to determine endpoints that cannot be accessed on the application for user whose subscription period expired and I will need to code a working service in middleware that will make them inaccessible. (Like to permission methods) This will both do extra coding work and each endpoint will require extra work.

These are my thoughts....

Or other solutions...

How should we restrict a user whose subscription expires and how should we act?

Thank you very much for your information sharing.


Solution

  • I solved the question I asked above using Method 2.

    I wanted to explain how I did it, as I thought it might help those who investigate this question in the future.

    I said in method 2, jwt has generated but membership restricted.


    First of all, when generating tokens, I set claims whether they have a subscription or not.

    ....
    new Claim(JwtClaimIdentifier.HasSubscription, hasSubscription)
    

    I do not explain here in detail. Standard claims.


    Subscription Control

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public sealed class SubscriptionRequiredAttribute : TypeFilterAttribute
    {
        public SubscriptionRequiredAttribute()
            : base(typeof(SubscriptionFilter)) { }
    }
    

    --

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public sealed class AllowWithoutSubscriptionAttribute : Attribute
    {
        public AllowWithoutSubscriptionAttribute() { }
    }
    

    --

    public class SubscriptionFilter : IAuthorizationFilter
    {
        private bool AllowWithoutSubscription(AuthorizationFilterContext context)
        {
            var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
    
            bool allowWithoutSubscriptionForMethod = controllerActionDescriptor.MethodInfo.CustomAttributes.Any(x => x.AttributeType == typeof(AllowWithoutSubscriptionAttribute));
            if (allowWithoutSubscriptionForMethod)
                return true;
    
            bool allowWithoutSubscriptionForController = controllerActionDescriptor.ControllerTypeInfo.CustomAttributes.Any(x => x.AttributeType == typeof(AllowWithoutSubscriptionAttribute));
            if (allowWithoutSubscriptionForController)
                return true;
    
            return false;
        }
    
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            if (AllowWithoutSubscription(context))
                return;
    
            var hasSubscription = context.HttpContext.User.Claims.First(x => x.Type == JwtClaimIdentifier.HasSubscription).Value.ToLower() == "true";
            if (!hasSubscription)
                context.Result = new BadRequestObjectResult(**ErrorCode**);
        }
    }
    

    I added, an attribute that override subscription control.

    For example; To use it in a controller or method that I need to override when checking subscriptions on base.

    Use Controller

    [SubscriptionRequired]
    public class FooController
    {
    public async Task<IActionResult> FooMethodOne(){...}
    
    public async Task<IActionResult> FooMethodTwo(){...}
    
    [AllowWithoutSubscription]
    public async Task<IActionResult> FooMethodThree(){...}
    }
    

    While FooMethodOne and FooMethodTwo above require subscription, FooMethodThree will work without subscription.

    Likewise, all controls are called "AllowWithoutSubscription". It can also be called "SubscriptionRequired" in methods.

    Hopefully it benefits your business...