Asp.net web API 2 Owin having both basic and bearer authentication

Cenk 1,036 Reputation points
2022-02-11T18:13:02.23+00:00

​Hi,

I have an asp.net web API and I would like to use different authentications (basic and bearer) in my different controllers. I can use bearer tokens but couldn't manage to use basic authentication as well.

Here is owin startup

public class Startup
    {
        public void Configuration(IAppBuilder app)
        {

            var config = GlobalConfiguration.Configuration;
            Configure(app, config.DependencyResolver);
            WebApiConfig.Register(config);
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            app.UseWebApi(config);
            config.EnsureInitialized();

        }

        private static void Configure(IAppBuilder app, IDependencyResolver resolver)
        {
            var options = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
                Provider = new AuthorizationServerProvider((IUserValidate)resolver.GetService(typeof(IUserValidate)))

            };

            app.UseOAuthAuthorizationServer(options);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

        }
    }

Here is Authorization Server Provider

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        private readonly IUserValidate _userValidate;
        public AuthorizationServerProvider(IUserValidate userValidate)
        {
            _userValidate = userValidate;
        }
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            if (!context.TryGetBasicCredentials(out var clientId, out var clientSecret))
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
            }

            if (_userValidate.Login(clientId, clientSecret))
            {
                context.Validated();
            }
        }


        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {


            if (_userValidate.Login(context.UserName, context.Password))
            {

                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                identity.AddClaim(new Claim("sub", context.UserName));
                identity.AddClaim(new Claim("role", "admin"));

                context.Validated(identity);
            }
            else
            {

                context.SetError("invalid_grant", "The user name or password is incorrect.");
            }
        }
    }

Basic Authentication Attribute

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
    {
        private const string Realm = "My Realm";
        private readonly Func<IUserValidate> _factory;

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


        public override void OnAuthorization(HttpActionContext actionContext)
        {

            if (actionContext.Request.Headers.Authorization == null)
            {
                actionContext.Response = actionContext.Request
                    .CreateResponse(HttpStatusCode.Unauthorized);

                if (actionContext.Response.StatusCode == HttpStatusCode.Unauthorized)
                    actionContext.Response.Headers.Add("WWW-Authenticate",
                        $"Basic realm=\"{Realm}\"");
            }
            else
            {

                var authenticationToken = actionContext.Request.Headers
                    .Authorization.Parameter;
                try
                {
                    //Decode the string
                    var decodedAuthenticationToken = Encoding.UTF8.GetString(
                        Convert.FromBase64String(authenticationToken));

                    var usernamePasswordArray = decodedAuthenticationToken.Split(':');

                    var username = usernamePasswordArray[0];

                    var password = usernamePasswordArray[1];

                    var 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);
                    }
                }
                catch
                {
                    actionContext.Response = actionContext.Request
                        .CreateResponse(HttpStatusCode.Unauthorized);
                }
            }
        }
    }

When I call ...API/v2/game/abc101/purchase I am getting 401, it is working. When I call http://localhost:52908/token I am getting token. But when I call another controller http://localhost:52908/api/v2/game/purchase basic-authentication does NOT work!

Thanks in advance.

Developer technologies ASP.NET Other
{count} votes

2 answers

Sort by: Most helpful
  1. Cenk 1,036 Reputation points
    2022-02-12T04:58:20.03+00:00

    Here is what happened @AgaveJoe

    If I uncomment the last line that registers basic authentication globally, basic authentication works perfectly without adding [Authorize] in the controller. But this time when I get the token and try to get the data from ...api/v2/game/abc101/purchase that has [Authorize] in the other controller, Basic authentication works and returns 401. By the way, I am using unity container.

    public static class WebApiConfig  
        {  
            public static void Register(HttpConfiguration config)  
            {  
                 
                config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;  
                config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling =  
                    DateTimeZoneHandling.Local;  
                  
                config.MapHttpAttributeRoutes();  
      
                config.Routes.MapHttpRoute(  
                    "DefaultApi",  
                    "api/{controller}/{id}",  
                    new {id = RouteParameter.Optional}  
                );  
      
                config.MessageHandlers.Add(new RequestResponseHandler());  
                 
                config.Filters.Add(new CustomExceptionFilter());  
    
                var resolver = config.DependencyResolver; //Assuming one is set.  
                var basicAuth = (BasicAuthenticationAttribute)resolver.GetService(typeof(BasicAuthenticationAttribute));  
                  
                if (basicAuth != null) config.Filters.Add(basicAuth);  
                  
      
            }  
        }  
    

  2. Cenk 1,036 Reputation points
    2022-02-16T17:54:52.17+00:00

    After long googling, here is how I managed to use both basic authentication and bearer authentication for my different controllers.

    In Custom basic authentication Attribute I used dependency and requestScope.GetService.

    public class CustomBasicAuthenticationAttribute : AuthorizationFilterAttribute
        {
            [Dependency] public static IUserValidate authentication { get; set; }
    
            private const string Realm = "My Realm";
    
    
            public override void OnAuthorization(HttpActionContext actionContext)
            {
                var requestScope = actionContext.Request.GetDependencyScope();
    
                //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",
                            $"Basic realm=\"{Realm}\"");
                }
                else
                {
                    //Get the authentication token from the request header
                    var authenticationToken = actionContext.Request.Headers
                        .Authorization.Parameter;
                    try
                    {
                        //Decode the string
                        var decodedAuthenticationToken = Encoding.UTF8.GetString(
                            Convert.FromBase64String(authenticationToken));
                        //Convert the string into an string array
                        var usernamePasswordArray = decodedAuthenticationToken.Split(':');
                        //First element of the array is the username
                        var username = usernamePasswordArray[0];
                        //Second element of the array is the password
                        var password = usernamePasswordArray[1];
    
                        authentication = requestScope.GetService(typeof(IUserValidate)) as IUserValidate;
    
                        if (authentication != null && authentication.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);
                        }
                    }
                    catch
                    {
                        actionContext.Response = actionContext.Request
                            .CreateResponse(HttpStatusCode.Unauthorized);
                    }
                }
            }
        }
    

    In one of my controller, I added those attributes

    [OverrideAuthentication]
            [CustomBasicAuthentication]
    
            [HttpPost, Route("purchase")]
            public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
            {
                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
    ...
    

    Now I can use bearer token authentication for ...api/v2/game/abc101/purchase and basic authentication for ...api/v2/game/purchase.

    Hope this solution helps others.

    The vital part is the dependency and actionContext.Request.GetDependencyScope();. Without OverrideAuthentication it is working as expected.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.