asp.net-coreauthorizationokta

Integrating Okta via a Authorization Filter


I have implemented Okta authentication into my asp.net core application via this tutorial:

https://developer.okta.com/blog/2022/04/20/dotnet-6-web-api

This is perfect for my SPA to authenticate with my api. However, there are different user types for accessing my API and this information is passed in the claims. I am trying to create a filter to make it very easy to implement for each endpoint I want to give access to. I have created a general filter and it works great but I do not pass any parameters:

public class OktaAuthorizeFilter : IAuthorizationFilter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly Okta.IJwtValidator _validationService;

        public OktaAuthorizeFilter(IHttpContextAccessor httpContextAccessor, IJwtValidator validationService)
        {
            _httpContextAccessor = httpContextAccessor;
            _validationService = validationService;
        }

        public async void OnAuthorization(AuthorizationFilterContext context)
        {
            var authToken = _httpContextAccessor.HttpContext!.Request.Headers["Authorization"].ToString();

            if (String.IsNullOrEmpty(authToken))
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }

            var validatedToken = await _validationService.ValidateToken(authToken.Split(" ")[1]);

            if (validatedToken == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }
        }
    }

    public class OktaAuthorizeAttribute : TypeFilterAttribute
    {
        public OktaAuthorizeAttribute() : base(typeof(OktaAuthorizeFilter))
        {
        }
    }

This works great as follows:

    [HttpGet(Name = "GetUserAccount")]
    [OktaAuthorizeAttribute]
    [EnableRateLimiting("api")]
    public IActionResult Get()
    {
        UserAccount TestAccount = new UserAccount() {InternalUserNumber = 3, EmailAddress = "test@mail.com"};
        var json = _userAccountService.Read(TestAccount);
        return Ok(json);
    }

To differentiate my user types the Okta JWT token has a claim that lists the user types. A user could be Type1 and Type2 or just Type1, etc. So I was hoping to implement my filter similar to how ms graph works as follows:

[OktaAuthorizeAttribute(Type = new string[] { "Type1", "Type2" })]

This way if a user satisfies one of these options then it will work. I am not sure however how to pass parameters to the filters to make this work and then how can I use them? Is this level of authorization possible?

EDIT 3/28/24

I have updated my code to the following and it runs but unfortunately it runs after my controller runs so it doesnt restrict access. Not sure why!

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using ARMS_API.Okta;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

namespace ARMS_API.Filters
{
    public class OktaAuthorizationFilter : IAuthorizationFilter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly string[]? _permissions;
        private readonly Okta.IJwtValidator? _validationService;
        public OktaAuthorizationFilter(IHttpContextAccessor httpContextAccessor, Okta.IJwtValidator validationService, string[] Permissions)
        {
            _httpContextAccessor = httpContextAccessor;
            _validationService = validationService;
            _permissions = Permissions;
        }
        public async void OnAuthorization(AuthorizationFilterContext context)
        {
            var authToken = context.HttpContext!.Request.Headers["Authorization"].ToString();

            //ensure that acccess token has data
            if (String.IsNullOrEmpty(authToken) || _validationService == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }
        
            var validatedToken = await _validationService.ValidateToken(authToken.Split(" ")[1]);

            if (validatedToken == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            } 

            var RoleClaim = validatedToken.Claims.Where(claim => claim.Type.ToString() == "role").FirstOrDefault();
            
            if(RoleClaim == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }
            
            var roles = RoleClaim.Value.Split(',');

            if(roles.Intersect(_permissions!).ToArray().IsNullOrEmpty())
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }
        }
    }

    public class OktaAuthorizationAttribute : TypeFilterAttribute
    {
        public OktaAuthorizationAttribute(params string[] Permissions) : base(typeof(OktaAuthorizationFilter))
        {
            Arguments = new object[] { Permissions };
        }
    }
}

And now the attribute shows like this above the controller:

[OktaAuthorization("usertype1", "usertype2")]

Solution

  • In order to utilize this approach to using a filter with parameters set by a attribute and filter you need to ensure you utilize the Arguments parameter as follows:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using APP.Okta;
    using System.Security.Claims;
    using System.IdentityModel.Tokens.Jwt;
    using Microsoft.IdentityModel.Tokens;
    
    namespace APP.Filters
    {
        public class OktaAuthorizationFilter : IAsyncAuthorizationFilter
        {
            private readonly IHttpContextAccessor _httpContextAccessor;
            private readonly string[]? _permissions;
            private readonly Okta.IJwtValidator? _validationService;
            public OktaAuthorizationFilter(IHttpContextAccessor httpContextAccessor, Okta.IJwtValidator validationService, string[] Permissions)
            {
                _httpContextAccessor = httpContextAccessor;
                _validationService = validationService;
                _permissions = Permissions;
            }
            public async void OnAuthorizationAsync(AuthorizationFilterContext context)
            {
                var authToken = context.HttpContext!.Request.Headers["Authorization"].ToString();
    
                //ensure that acccess token has data
                if (String.IsNullOrEmpty(authToken) || _validationService == null)
                {
                    context.Result = new UnauthorizedObjectResult(string.Empty);
                    return;
                }
            
                var validatedToken = await _validationService.ValidateToken(authToken.Split(" ")[1]);
    
                if (validatedToken == null)
                {
                    context.Result = new UnauthorizedObjectResult(string.Empty);
                    return;
                } 
    
                var RoleClaim = validatedToken.Claims.Where(claim => claim.Type.ToString() == "roles").FirstOrDefault();
                
                if(RoleClaim == null)
                {
                    context.Result = new UnauthorizedObjectResult(string.Empty);
                    return;
                }
                
                var roles = RoleClaim.Value.Split(',');
    
                if(roles.Intersect(_permissions!).ToArray().IsNullOrEmpty())
                {
                    context.Result = new UnauthorizedObjectResult(string.Empty);
                    return;
                }
            }
        }
    
        public class OktaAuthorizationAttribute : TypeFilterAttribute
        {
            public OktaAuthorizationAttribute(params string[] Permissions) : base(typeof(OktaAuthorizationFilter))
            {
                Arguments = new object[] { Permissions };
            }
        }
    }
    

    THen to utilize the variable as a parameter in the attribute tag do as follows:

    [OktaAuthorization("usertype1", "usertype2")]