-2

I have an asp.net web api and I would like to use basic authentication. Is there any way to make this loosely coupled? I tried constructor DI but I couldn't figure it out how to pass Dbcontext into WebApiConfig. Any help would be appreciated.

Here is my Interface:

public interface IUserValidate
    {
        bool Login(string username, string password);
    }

Here is my class:

public class UserValidate : IUserValidate
    {

        //This method is used to check the user credentials
        public bool Login(string username, string password)
        {
            using (var context = new EPINMiddleWareAPIContext())
            {
                return context.Companies.Any(user =>
                    user.userName.Equals(username, StringComparison.OrdinalIgnoreCase)
                    && user.password == password);
            }

        }
    }

Here is my Basic Authentication Filter:

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
    {

        private const string Realm = "My Realm";


        public override void OnAuthorization(HttpActionContext actionContext)
        {
            //If the Authorization header is empty or null
            //then return Unauthorized
            if (actionContext.Request.Headers.Authorization == null)
            {
                actionContext.Response = actionContext.Request
                    .CreateResponse(HttpStatusCode.Unauthorized);
                // If the request was unauthorized, add the WWW-Authenticate header 
                // to the response which indicates that it require basic authentication
                if (actionContext.Response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    actionContext.Response.Headers.Add("WWW-Authenticate",
                        string.Format("Basic realm=\"{0}\"", Realm));
                }
            }
            else
            {
                //Get the authentication token from the request header
                string authenticationToken = actionContext.Request.Headers
                    .Authorization.Parameter;
                //Decode the string
                string decodedAuthenticationToken = Encoding.UTF8.GetString(
                    Convert.FromBase64String(authenticationToken));
                //Convert the string into an string array
                string[] usernamePasswordArray = decodedAuthenticationToken.Split(':');
                //First element of the array is the username
                string username = usernamePasswordArray[0];
                //Second element of the array is the password
                string password = usernamePasswordArray[1];
                //call the login method to check the username and password
                UserValidate uv = new UserValidate();
                if (uv.Login(username, password))
                {
                    var identity = new GenericIdentity(username);
                    IPrincipal principal = new GenericPrincipal(identity, null);
                    Thread.CurrentPrincipal = principal;
                    if (HttpContext.Current != null)
                    {
                        HttpContext.Current.User = principal;
                    }
                }
                else
                {
                    actionContext.Response = actionContext.Request
                        .CreateResponse(HttpStatusCode.Unauthorized);
                }
            }
        }

    }

Here is my WebApiConfig:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            config.Filters.Add(new BasicAuthenticationAttribute());
            // Web API routes
            config.MapHttpAttributeRoutes();
            //Registering GlobalExceptionHandler
            config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
            //Registering UnhandledExceptionLogger
            config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
            //Registering RequestResponseHandler
            config.MessageHandlers.Add(new RequestResponseHandler());
            //Validate Token
            //config.MessageHandlers.Add(new TokenValidationHandler());

            //Registering CustomExceptionFilter
            config.Filters.Add(new CustomExceptionFilter());

        }
    }

Here is my Dbcontext:

public class EPINMiddleWareAPIContext : DbContext
    {

        public EPINMiddleWareAPIContext() : base("name=EPINMiddleWareAPIContext")
        {
        }

        public DbSet<InitiateRequest> InitiateRequests { get; set; }
        public DbSet<InitiateResponse> InitiateResponses { get; set; }
        public DbSet<Company> Companies { get; set; }
        public DbSet<ConfirmRequest> ConfirmRequests { get; set; }
        public DbSet<ConfirmResponse> ConfirmResponses { get; set; }
        public DbSet<GameBank> GameBanks { get; set; }
        public DbSet<GameCouponBank> GameCouponBanks { get; set; }

    }

Here is my Ninject Web Common:

using EPINMiddleWareAPI.Controllers;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(EPINMiddleWareAPI.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(EPINMiddleWareAPI.App_Start.NinjectWebCommon), "Stop")]

namespace EPINMiddleWareAPI.App_Start
{
    using System;
    using System.Web;

    using Microsoft.Web.Infrastructure.DynamicModuleHelper;

    using Ninject;
    using Ninject.Web.Common;
    using Models;
    using Ninject.Web.Common.WebHost;

    public static class NinjectWebCommon 
    {
        private static readonly Bootstrapper bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start() 
        {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
            bootstrapper.Initialize(CreateKernel);
        }

        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop()
        {
            bootstrapper.ShutDown();
        }

        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            try
            {
                kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
                RegisterServices(kernel);
                return kernel;
            }
            catch
            {
                kernel.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<EPINMiddleWareAPIContext>().ToSelf().InRequestScope();


        }        
    }
}

Edit: I tried to implement provider as follows based on Nkosi's solution:

public class AuthenticationFilterProvider : System.Web.Http.Filters.IFilterProvider
    {
        private readonly Func<BasicAuthenticationAttribute> _authorizeViewFilterFactory;

        public AuthenticationFilterProvider(Func<BasicAuthenticationAttribute> authorizeViewFilterFactory)
        {
            this._authorizeViewFilterFactory = authorizeViewFilterFactory;
        }

        public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
        {
            if (!actionDescriptor.GetCustomAttributes<BasicAuthenticationAttribute>().Any())
                return Enumerable.Empty<FilterInfo>();

            return new[]
            {
                new FilterInfo(this._authorizeViewFilterFactory(), FilterScope.Action)
            };
        }
    }

And this binding to ninject:

kernel.Bind<System.Web.Http.Filters.IFilterProvider>().To<AuthenticationFilterProvider>();

But basic authentication does not fired.

Cenk
  • 13
  • 1
  • 10
  • Read up `IDependencyResolver`, integrate your DI with the web api and resolve your desired dependencies as needed via constructor injection for your classes and service locator pattern for your attributes/filters. – Nkosi Jun 12 '19 at 11:08
  • @Nkosi I tried but couldn't figure it out how to pass Dbcontext into WebApiConfig. – Cenk Jun 12 '19 at 11:11
  • Why do you need to pass DbContext to WebApiConfig? – Nkosi Jun 12 '19 at 11:12
  • @Nkosi in BasicAuthenticationAttribute I am calling UserValidate in order to query database. In UserValidate I pass Dbcontext into the constructor. How can I call UserValidate without creating UserValidate inside of BasicAuthenticationAttribute. – Cenk Jun 12 '19 at 11:17
  • Can I bind UserValidate into BasicAuthenticationAttribute in order to call Login? – Cenk Jun 12 '19 at 11:23
  • If you are not planning on using the attribute to decorate anything and are only going to use it in in config then sure. – Nkosi Jun 12 '19 at 11:25
  • so should I pass IUserValidate into BasicAuthenticationAttribute via the constructor and then bind in ninject something like this? `kernel.Bind().To();` – Cenk Jun 12 '19 at 11:29
  • No. you bind the interface to its implementation, the attribute to itself, you resolve the attribute in config and add it to filters – Nkosi Jun 12 '19 at 11:31
  • you mean sonething like this? `kernel.Bind().To();` I don't get what you mean by add it to filter – Cenk Jun 12 '19 at 11:33
  • You should use a FilterProvider. See https://stackoverflow.com/a/48647030/1236044 It will also allow using passive attributes ( https://blog.ploeh.dk/2014/06/13/passive-attributes/ ) – jbl Jun 12 '19 at 16:37
  • @jbl I tried to add provider but basic authentication does not fire. Can you check my edited code? – Cenk Jun 12 '19 at 17:49
  • il you want to enable your filter for all actions, just remove the `if` clause in the `GetFilters` method – jbl Jun 13 '19 at 08:03

1 Answers1

0

refactor UserValidate

public class UserValidate : IUserValidate {
    private EPINMiddleWareAPIContext context;

    public UserValidate(EPINMiddleWareAPIContext context) {
        this.context = context;
    }

    //This method is used to check the user credentials
    public bool Login(string username, string password) {
        return context.Companies.Any(user =>
            user.userName.Equals(username, StringComparison.OrdinalIgnoreCase)
            && user.password == password
        );
    }
}

If you are not planning on using the attribute to decorate anything and are only going to use it in in config then it can be refactored to use constructor injection with a factory to get the service.

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute {

    private const string Realm = "My Realm";
    readonly Func<IUserValidate> factory;

    public BasicAuthenticationAttribute(Func<IUserValidate> factory) {
        this.factory = factory;
    }

    public override void OnAuthorization(HttpActionContext actionContext) {
        //If the Authorization header is empty or null
        //then return Unauthorized
        if (actionContext.Request.Headers.Authorization == null) {
            actionContext.Response = actionContext.Request
                .CreateResponse(HttpStatusCode.Unauthorized);
            // If the request was unauthorized, add the WWW-Authenticate header 
            // to the response which indicates that it require basic authentication
            if (actionContext.Response.StatusCode == HttpStatusCode.Unauthorized) {
                actionContext.Response.Headers.Add("WWW-Authenticate",
                    string.Format("Basic realm=\"{0}\"", Realm));
            }
        } else {
            //Get the authentication token from the request header
            string authenticationToken = actionContext.Request.Headers
                .Authorization.Parameter;
            //Decode the string
            string decodedAuthenticationToken = Encoding.UTF8.GetString(
                Convert.FromBase64String(authenticationToken));
            //Convert the string into an string array
            string[] usernamePasswordArray = decodedAuthenticationToken.Split(':');
            //First element of the array is the username
            string username = usernamePasswordArray[0];
            //Second element of the array is the password
            string password = usernamePasswordArray[1];
            //call the login method to check the username and password
            IUserValidate uv = factory(); 
            if (uv.Login(username, password)) {
                var identity = new GenericIdentity(username);
                IPrincipal principal = new GenericPrincipal(identity, null);
                Thread.CurrentPrincipal = principal;
                if (HttpContext.Current != null) {
                    HttpContext.Current.User = principal;
                }
            } else {
                actionContext.Response = actionContext.Request
                    .CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
    }
}

register your service(s) accordingly

private static void RegisterServices(IKernel kernel) {
    kernel.Bind<EPINMiddleWareAPIContext>().ToSelf().InRequestScope();
    kernel.Bind<IUserValidate>().To<UserValidate>();
    kernel.Bind<BasicAuthenticationAttribute>().ToSelf();
}

you resolve the attribute in config and add it to filters

public static class WebApiConfig {
    public static void Register(HttpConfiguration config) {

        var resolver = config.DependencyResolver; //Assuming one is set.
        var basicAuth = resolver.GetService(typeof(BasicAuthenticationAttribute)) as BasicAuthenticationAttribute;
        // Web API configuration and services
        config.Filters.Add(basicAuth);

        //...omitted for brevity
Nkosi
  • 235,767
  • 35
  • 427
  • 472