Securing Legacy Web-Service site using anonymous authentication for local access - asp.net

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.

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.

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
Add the compiled DLL (FilterModule.dll) to the Web Service (site) bin Directory.
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" />

Related

Is it possible to restrict access to REST API built using Web API 2?

I built a REST API using ASP.NET Web API 2, so I could deliver data from a backend database to my applications running on any platform (mobile, web, desktop etc) However up until now, I simply call the website with the controller I need data from and that's it, it sends back the JSON string in the response.
But, the data is kind of special, and there is nothing to prevent another developer from simply calling the controllers and getting back the exact same data and building their own application around it.
My question is - is there anyway to restrict access to the API so that only my applications can get valid response from the server. (i.e. prevent other developers from using my REST API)
I already read these documentation Security, Authentication, and Authorization in ASP.NET Web API I'm just not sure which of these scenarios apply to me, or if any will do what I am asking.
EDIT - Another piece of info, my web service is running on Azure in case it is relevant.
Did you happen to check token based authentication?Please go through https://stackoverflow.com/a/38670221/4868839 and https://www.youtube.com/watch?v=rMA69bVv0U8 must be a good to start with.
there are different way to validate your web api.
Authentication Filters in ASP.NET Web API 2
using you can customise your authentication filter
you can refer sample Reference link
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
//App_Start/Startup class
public void ConfigureAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/v1/accesstoken"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(AppConfiguration.AccessTokenExpireDuration),
Provider = new SampleOAuthProvider() // class that override your method
};
// Token Generation
app.UseOAuthBearerTokens(OAuthServerOptions);
}
You can find reference from inherits Default implementation of IOAuthAuthorizationServerProvider used by Authorization
i hope it sholud helps you thanks.

ASP.NET MVC with Forms Auth and WebApi with Basic Auth

I have a WebApi using Basic Auth nicely. And I have an MVC site using Forms Auth nicely. But here's the catch:
Client X has a dedicated database with any number of Contacts and Products. The MVC site is a dedicated site for them (via {clientId} routing), which allows their Contacts to log in (via Forms Auth) and place orders for their products. The Contact must be Form-ly logged in to place an order.
The product orders (need to) hit the WebApi to be recorded in the Client's database.
But since the WebApi uses Basic Auth to validate the Client, not the Contacts who placed the orders, every request comes back is 401 - Unauthorized.
I've checked out ThinkTecture as suggested by a number of posts here on SO, however it doesn't get me what I need because I'm not looking to allow Forms Auth in the WebApi. I don't want to authenticate the Contact from the Client's database in the WebApi, I want to authenticate the Client in the WebApi.
Has anyone come across a similar scenario and am I missing something glaringly obvious? Perhaps I need to implement both Forms and Basic on the site?
The very standard Api call I'm making from the site (where the UserName and Password are the Client's, not the Contact's):
var clientId = new Guid(RouteData.Values["clientId"].ToString());
var baseUrl = ConfigurationManager.AppSettings["ApiBaseAddress"];
var authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(String.Format("{0}:{1}", _shoppingCartSettings.UserName, _shoppingCartSettings.Password)));
var requestUrl = String.Format("api/{0}/inventory", clientId.ToString());
var httpWebRequest = WebRequest.Create(baseUrl + requestUrl);
httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, "Basic " + authHeader);
httpWebRequest.Method = "GET";
httpWebRequest.Accept = "application/json";
httpWebRequest.ContentType = "application/json";
try
{
using (var httpWebResponse = httpWebRequest.GetResponse())
{
// we never get here because of a 401
}
}
catch (WebException ex)
{
using (var httpWebResponse = ex.Response)
{
// we always get here
}
}
If I set up a separate test client and make the same call, it works great :/
Is your Web API under the same virtual directory and configuration as the MVC site? It looks like the Forms Auth HTTP module kicks in for your API, which you don't want. As long as you don't plan to call the API directly from the browser, move it to a separate virtual directory that is set up exclusively for basic auth, no forms auth module in the web.config for the API.
Why not have one login for your MVC site that has the ability to submit orders for every Client? It makes sense for your WebAPI to only allow Clients to submit orders for themselves. But I don't think it makes sense to have your MVC site authenticate as different Clients based on the Contact. Your MVC site would have to store the passwords for each Client.
Instead, create one login for the MVC site and give it the ability to submit an order for any Client.
After much banging of head against the not-so-proverbial wall, and a much needed shove by #0leg, I've discovered the cause.
In the Properties of my WebApi project file under Web > Servers, the Visual Studio Development Server was being used with a Virtual Path of "/", whereas my MVC project file was set up to use the Local IIS Web Server. The MVC project also had the Apply server settings to all users (store in project file) option checked.
Setting both to use the local IIS server resolved it.
Upon further contemplation, this now seems logical since they were essentially running on different servers.
Posting this for posterity's sake.

WCF - Third party application authentication

I am currently working on an iPhone application. This application calls back to WCF services exposed through my ASP.NET web application. Currently, my WCF operation looks like the following:
[OperationContract]
[WebInvoke(Method = "POST")]
public string SubmitMessage(string message, int priority)
{
try
{
// Process message
// Return success code | message
}
catch (Exception)
{
// Return error code | message
}
}
My web application is using ASP.NET Forms Authentication. My challenge is, I only want authenticated users to be able to call this operation from their iPhone. I know that the iPhone SDK has baked in support for XML. However, I’m not sure how to lock down my WCF operation such that only authenticated users can access it.
How do I make my WCF operation only accessible to authenticated users from third party applications?
Thank you
This has to be done in both sides of the transfer, namely server (WCF site) and client (iPhones).
If you're using SOAP endpoints then you should look for Objective-C SOAP client libraries. This way all you will have to do is to setup best security options to your needs and your server-side code will be almost identical as it is at the moment.
Instead if you're exposing as RESTful endpoints I suggest you to look for an OpenID(&OAuth) approach. Here also, there are .NET and Objective-C libraries available. I belive this solution would require much more coding in both of the sides.

WCF Forms Based Authentication Via Web App - Passing Credentials

I have a simple web service whereby the security is handled via forms based authentication.
WCFTestService.ServiceClient myService = new
WCFTestService.ServiceClient();
myService.ClientCredentials.UserName.UserName = "user";
myService.ClientCredentials.UserName.Password = "secret";
lblResult.Text = myService.GetData(1231);
myService.Close();
I'm accessing this via a web app. So I want to do the above once but for security/performance not have to do it again. I was thinking something like the the below but as I'm using FormsAuthentication this wont work...
//Obtain the authenticated user's Identity and impersonate the original caller
using (((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate())
{
WCFTestService.ServiceClient myService2 = new WCFTestService.ServiceClient();
lblResult.Text = "From Logged On Credentials"+myService2.GetData(1231);
myService2.Close();
}
What you're trying to do is establish a "secure session" between your client and your service. This is a concept that will only work with the wsHttpBinding - so if you're not using that particular binding, it won't work.
To establish a secure session, you need to set a number of specific config properties in the client and server's config files - you can certainly find those settings by reading the docs (look for "establishSecurityContext") or check out Michele Leroux Bustumante's excellent WCF screencast on security fundamentals on MSDN.
But really: I wouldn't recommend trying to use secure session by all means. Under normal circumstances, using per-call services is the preferred option, and the overhead for re-authenticating with each service call is really negligable.
Marc

Custom HTTP Basic Authentication for ASP.NET Web Services on .NET 3.5/VS 2008

I am refactoring a working ASP.NET Web Application to expose Web Services interface using ASP.NET Web Service. According to Web Services authentication - best practices, Basic Auth over https is the way to go. Let's assume it is, as opposed to doing WS-Security, X509, etc..
On .NET 3.5/VS 2008, what's the simplest way of implementing custom http Basic Authentication (non-Windows account), for example, accepting only if user name is "foo" and password is "bar". Ultimately, I'd like Thread.CurrentPrincipal set.
Do I write my own HttpModule or can this be done simpler?
Likely using Custom Basic Authentication for IIS, written by Dominick Baier is the way to go. As he points out WCF 3.5's usernames over transport security cannot be used on IIS-hosted service, although my question was regarding ASP.NET Web Services not WCF.
There's another implementation of HTTP Module called Basic authentication in ASP.NET against custom datasource by Santosh Sahoo.
Although it's not what I wanted, I found QuickStart Tutorial's SOAP Headers sample to be informative workaround. Sending password in plain text over http is clearly insecure, but this example could be extended to add more security, for instance running on https or sending hash of "password + one-time GUID + timestamp".
Grab the value of the Authorization header, parse it and validate the username/password.
The value is username:password, encoded as a Base64 string.
See http://en.wikipedia.org/wiki/Basic_access_authentication for details.
Edit: if you want this done for every request, using the custom auth scheme, then it would be easier to write an HttpModule to handle parsing the header and setting the thread's principal.
If you are considering WCF, you can use usernameOverTransport security for basicHttpBinding. i.e. username and passowrd reside in the SOAP header of the request and all the traffic are protected by SSL encryption over the wire.
A custom UserNamePasswordValidator validator can be used to authenticate the incoming credentials against e.g. database.
You can set the thread principal within a custom IAuthorizationPolicy in the service behavior.
e.g. Evaluate method implementation for IAuthorizationPolicy for setting current principal
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
Object obj;
if( evaluationContext.Properties.TryGetValue( "Identities", out obj ))
{
// get the authenticated identity
IIdentity client = (obj as IList<IIdentity>)[0];
evaluationContext.Properties["Principal"] = ... // create principal obj here for the identity
// this will set thread's current principal
}
return true;
}

Resources