1

I have a legacy WebForms application that consumes (ASP.net) Web-Services (using anonymous access) that reside on the same IIS server (same domain, different apps each with their own dedicated app pool). This application was hosted in a secured VPN but now there is a request to host it publicly.

What is the expedient (yet secure) method to secure the web services site without doing a substantial re-coding (on the application level). I have tried configuring the Web-Service site in IIS so that it enables only the Service account (Identity) under which the Web Applications App Pool runs but the current request mode coming from the application is always using Anonymous access. I need to restrict access to allow only the this specific web application. I am thinking of an ISAPI filter, but it is not recommended for IIS 7+ ?

ADDENDUM: I would love to find an IIS based solution. Currently I am trying to restrict access based to just the Web Application source. Problem is (as stated) is those request are all "Anonymous" if I could make those requests use "Network Service" or some other local Identity then I would be set.

Cœur
  • 37,241
  • 25
  • 195
  • 267
DaniDev
  • 2,471
  • 2
  • 22
  • 29
  • _method to secure the web services site without doing a substantial re-coding_. If I understand correctly you just need to disable anonymous authentication and enable, say, forms authentication in the folder in IIS, Then every call needs to be authenticated. No code changes required – Nick.Mc Sep 06 '17 at 04:25
  • I don't understand your today configuration. Do you have a web server, one web site, two web application: http ://myserver/appWebForm and http ://myserver/appAsmx ? Today myserver is on private / vpn network? – Fabrizio Accatino Sep 06 '17 at 09:32
  • @Nick.McDermaid I believe your suggestion would involve passing credentials on each web request and there are dozens if not hundreds of them, so I would rather authenticate base on the request origin so that I don't have to modify the existing code base for each request. – DaniDev Sep 06 '17 at 15:51
  • @Fabrizio Accatino the configuration today is the same as before 2 web apps (sites) sitting under the same Default Web Site (each with their own web.config) the only thing that changed from previous deployment to today is that the entire server was on a VPN so securing access to the Web Service application (SOAP XML) was not a concern. Now we want to move the entire architecture to a public server so we need to lock down (secure) calls to the Web services application. – DaniDev Sep 06 '17 at 16:01
  • Not sure if this could fit your needs but have you considered to simply move the web-service application to a different web-site, bound to a not public exposed tcp port? If you want, on this new web-site, you could even restrict connections only coming from 127.0.0.1/local_ip_address. – Fabrizio Accatino Sep 07 '17 at 06:40
  • @FabrizioAccatino Yes, I agree that is a possible alternative that I have considered. We have other deployments of other applications that mirror this configuration. Currently I was hoping to avoid the infrastructure overhead as we are still in the testing phase. – DaniDev Sep 07 '17 at 17:51

2 Answers2

2

I recommend you to use IdentityServer and OpenIdConnect to do that.

IdentityServer is a .NET/Katana-based framework and hostable component that allows implementing single sign-on and access control for modern web applications and APIs using protocols like OpenID Connect and OAuth2. It supports a wide range of clients like mobile, web, SPAs and desktop applications and is extensible to allow integration in new and existing architectures.

OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner. https://connect2id.com/learn/openid-connect

Using this, you can ask identity server to give you an access token and Id Token.

An identity token represents the outcome of an authentication process. It contains at a bare minimum an identifier for the user (called the sub aka subject claim). It can contain additional information about the user and details on how the user authenticated at the OP.

An access token allows access to a resource. Clients request access tokens and forward them to an API. Access tokens contain information about the client and the user (if present). APIs use that information to authorize access to their data.

In your case, you can for example implement the client credentials flow between your Webforms app and the WebService. (In this flow you are not gonna ask nothing to the users of the WebForms app). So, the idea is that the WebForms app is going to ask identity server to give it an access token to access the webservices resources. In the WebService you have to implement the authorization based on whatever you want (Scopes, claims etc). Please read LeastPrivilege blog (Dominick Baier), He is the master of these topic along with his buddy Brock Allen. Since I cannot post more than 1 link in StackOverflow which is really bad, you have to google them or google any additional information.

If you want a user authentication, you can use Implicit, Code or Hybrid flow. But that depends on what you really want to do.

I reckon you may have to do a bit of code but it's not too much. You can figure out a way to ask for authorization before any endpoint is reached.

I hope I was clear. If not, please ask me for more explanation.

  • Thank you Daniel for your well thought out and well documented response. I am somewhat familiar with OAuth 2.0 but have never implemented the token acquisition process so there is a bit of a learning curve there for me. While your answer is viable alternative I am hoping for something quick and easy such as modifying the authentication or authorization on the IIS level. I have up voted your answer and if I end up using it I will accept it. Thanks again and I may end up asking you a question or 2. – DaniDev Sep 06 '17 at 16:29
  • Hello Daniel. I was hoping you still available to provide more explanation? Specifically I was interested in "Implicit, Code or Hybrid flow". I am looking now looking at securing web services used by our Desktop app. From my research it would appear that I should use the 'password' or 'client_credentials' grant types? can 'implicit' seems to be not recommended now and instead 'Authorization Code' is recommended which does not seem practical for Desktop clients? – DaniDev May 14 '18 at 17:25
  • @DaniDev. I had to put my answer in multiple message because stackoverflow set a limit of characters If the application itself wants to make a call to a WebApi endpoint I would use client credentials flow (for example no user is logged-in and you need to request some content that it's not user related). https://auth0.com/docs/api-auth/grant/client-credentials. – Daniel Botero Correa May 16 '18 at 11:11
  • If the desktop app wants to make calls to a WebApi endpoint on behalf of a user (For example, user wants to see her user settings or any information she needs to be authorized beforehand to see). The user needs a token. To obtain this token I would use Resource Owner Password Credentials Grant since the desktop application you are working on is trusted with user credentials and it's desktop (So I guess redirections are not possible). https://auth0.com/docs/api-auth/grant/password – Daniel Botero Correa May 16 '18 at 11:11
  • Why two different flows? Client Credentials flow because the endpoints that the desktop app is making calls to are nothing to do with the user whatsoever. It's the desktop application the one that have to be authenticated and authorized to perform those actions. – Daniel Botero Correa May 16 '18 at 11:11
  • Resource Owner Password Credentials Grant because as soon as you want to do something on behalf an user, the application has to use the user's access token to make the calls to the api so the endpoint can decides whether or not that user is allowed to execute the code inside the endpoint. (authorization must happen at the endpoint level and one usually use the claims inside the access token to decide whether the user can perform an action or not) https://www.youtube.com/watch?v=EJeZ3YNnqz8 – Daniel Botero Correa May 16 '18 at 11:11
  • Resource Owner Password Credentials Grant should only be used when redirect-based flows (like the Authorization Code Grant) are not possible. This is my point of view based on your comment but you have to take into account several variables to take a decision. That's why I suggest you to read this article: https://auth0.com/docs/api-auth/which-oauth-flow-to-use – Daniel Botero Correa May 16 '18 at 11:11
  • Thank you @/Daniel. I think I follow I will read more extensively about Resource Owner Password Credentials Grant . Quick Question this would require an in-house authorization server, correct? – DaniDev May 16 '18 at 19:35
  • @DaniDev you have services on the internet that offers oauth2 authentication but you can do it yourself as well. I recommend you IdentityServer4 with OpenIdConnect. It seems they are the best. – Daniel Botero Correa May 17 '18 at 09:19
1

After more research I have settled on an http Module Solution, as it has the following benefits:

• Minimal Coding

• No need to modify existing code base

• Easy deployment

• Follows existing ASP.Net security models for (Local access)

The Module (VS:DLL Project)

using System;
using System.Web;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FilterModule
{
    class AuthorizeLocal : IHttpModule
    {
        public void Init(HttpApplication app)
        {
            app.BeginRequest += new EventHandler(OnBeginRequest);
        }


        private void OnBeginRequest(Object s, EventArgs e)
        {
            HttpApplication app = s as HttpApplication;
            HttpRequest req = app.Request;
            HttpContext context = app.Context;

            if (!req.IsLocal)    // Is the request from a Local Source?
            {
                context.Response.Close(); // close the response: ends request
            }

            /* Optional Test Code - to view locally create an html page TestModule.html in target site */
            string Identity = Thread.CurrentPrincipal.Identity.Name;
            string filePath = context.Request.FilePath;
            string fileExtension = VirtualPathUtility.GetExtension(filePath);
            string fileName = VirtualPathUtility.GetFileName(filePath);

            if (fileName.ToLower().Equals("testmodule.html"))
            {
                try
                {
                    app.Context.Response.Write("app: " + app.ToString());
                    context.Response.Write("<br/>server: " + app.Server.ToString());
                    context.Response.Write("<br/>Thread.CurrentPrincipal.Identity.Name: " + Thread.CurrentPrincipal.Identity.Name);
                    context.Response.Write("<br/>HttpRequest: " + req.Url.ToString());
                    context.Response.Write("<br/>req.UserHostName: " + req.UserHostName);
                    context.Response.Write("<br/>req.UserHostAddress: " + req.UserHostAddress);
                    context.Response.Write("<br/>filePath: " + filePath);
                    context.Response.Write("<br/>fileName: " + fileName);
                    context.Response.Write("<br/>fileExtension: " + fileExtension);
                    context.Response.Write("<br/>req.IsLocal: " + req.IsLocal.ToString());
                    context.Response.Write("<br/>req.LogonUserIdentity: " + req.LogonUserIdentity);
                    context.Response.Write("<br/>req.UserHostName : " + req.UserHostName);
                    context.Response.Write("<br/>req.AnonymousID " + req.AnonymousID);
                    context.Response.Write("<br/>req.IsAuthenticated : " + req.IsAuthenticated);
                }
                catch (Exception Ex)
                {
                    context.Response.Write("<br/> " + Ex.ToString());
                }
            }

            //if (_eventHandler != null)
            //    _eventHandler(this, null);
        }

        public void Dispose()
        {

        }

    }
}

Implementation

  1. Add the compiled DLL (FilterModule.dll) to the Web Service (site) bin Directory.

  2. Add the following to module definition in the Web Service (or site) configuration file (web.config)

in the <system.webServer> section under <modules>
add the following:

<add name ="FilterModule" type="FilterModule.AuthorizeLocal" />
DaniDev
  • 2,471
  • 2
  • 22
  • 29