Tải bản đầy đủ
Objective 4.3: Secure a Web API

Objective 4.3: Secure a Web API

Tải bản đầy đủ

Security needs should be determined by carefully weighing the costs and the benefits, and
those needs usually exist in a rather delicate balance. Web APIs expose corporate resources
to the Internet, which makes security a broader concern than more limited or sandboxed applications. The fact that you can access Web APIs by simple use of a web browser makes them
particularly useful and opens them up to just about every technology and platform out there.
At the same time, this openness carries with it an increased attack vector precisely because
access is so simple. There are many very useful features to make sure that your Web API is
only used by people you want to use it and that they use it in ways you intend for them to.
Fortunately, the leading authentication mechanisms employed by Web API developers are
the standard ones built into IIS that you are mostly likely familiar with if you have implemented secure web applications over the years.
For the exam, it’s important to understand the different authentication strategies you can
use. You also need to understand the threats, such as cross-site request forgery (XSFR), that
face your Web API and that you need to protect against.

This objective covers how to:
■■

Authenticate and authorize users

■■

Implement HTTP Basic authentication over SSL

■■

Implement Windows Authentication

■■

Enable cross-domain requests

■■

Prevent cross-site request forgery

■■

Implement and extend authorization filters

Authenticating and authorizing users
Although Web APIs are usually designed to allow easy access to the services, there will be
people you specifically want to access the service and people you want to prohibit from accessing it. You’ll want customers, licensed users, and perhaps other employees to be able to
access the service. People who have been blocked, spammers, license violators, and other
random miscreants should be restricted.
Even though you might specifically allow someone to use your service, there are things
you’ll probably want to restrict them from doing (even if you assume that no one has any
malevolent intentions, there are still reasons you want to restrict what can be done).



Objective 4.3: Secure a Web API

CHAPTER 4

325

Authentication
Authentication is the process of knowing the identity of a given user. If you have a subscription service, you want to make sure that the subscriber is the one who’s using the service.
Your exact needs will vary from application to application and company to company. In some
cases, you might be comfortable with validating a user’s identity via a user name and password combination. Sometimes your needs are much more specific, however.
If you have a subscription service in which a user name and password are the only requirements for authentication, someone could post the credentials to a public forum, and several
people could use the account in direct violation of your license policy. There are well-known
and well-meaning services that provide such information so that people can bypass registration systems to read content. If it’s important to you that only one user employs a given
account/password combination, you need to take many more steps to ensure they are
authenticated correctly. Fortunately, the Web API framework has a rich set of tools to employ
authentication, so the scheme you use for your given service can range from nonexistent to
quite sophisticated.

Authorization
Authorization is the process of deciding whether a given user can perform a given action. You
might allow a given user to access your service, but limit the number of requests the user can
make in a given time period. You might want to restrict the operations the user can perform
(for instance, executing read-only queries as an anonymous user, inserts as an authenticated
user, and updates as an administrative user).
The degree of control you exert over each of these can and will vary with the application;
experience with the users; resources you have available; and many, many more factors.

Implementing HttpBasic authentication
HttpBasic authentication is the simplest and easiest to use of each of the available mechanisms. Basic authentication provides a simple user name/password authentication mechanism
over plaintext. They are Base64–encoded header values that most web technologies that require authentication can work with. Table 4-4 lists some of the advantages and disadvantages
of HttpBasic authentication.
MORE INFO  BASIC AND DIGEST ACCESS AUTHENTICATION

Basic authentication is defined in RFC 2617. It’s a mature and well-known mechanism, so it
can be easily used for most authentication scenarios that aren’t overly complicated. To read
the RFC and learn more of the generic details, see http://www.ietf.org/rfc/rfc2617.txt.
Digest authentication is the next simplest form of authentication. However, instead of credentials being sent in plaintext, they are encrypted with some input from the server.

326

CHAPTER 4

Creating and consuming Web API-based services

TABLE 4-4  Trade-offs related to HttpBasic authentication

Advantage

Disadvantage

Well known, mature internet standard

User credentials are transmitted in the request.

All major browsers support it (Internet
Explorer, Safari, Firefox, Chrome, Opera,
etc.)

The credentials are not just part of the request; by default they
are transmitted in plaintext. This makes them vulnerable to interception unless additional steps are measured.

Simplest of the currently implemented
protocols

User credentials continue to be sent in subsequent requests.

Natively supported by Internet
Information Services (IIS)

There is no way to explicitly log out after authentication except
by explicitly ending the browser session.
Highly vulnerable to XSRF unless additional measures are taken.

Server-side processing
The list of strengths and weaknesses of Basic authentication in Table 4-4 helps to set the context for how Basic authentication works. Processing a request for a resource protected with
Basic authentication proceeds as follows:
1. An initial request is executed. If the resource necessitates Basic authentication, a

response code of Unauthorized (401) is returned. This response includes a WWWAuthentication header that specifies that the server supports Basic authentication.
2. A subsequent request is sent to the server, this time with the client credentials con-

tained in the Authorization header.
The WWW-Authentication header for basic authentication resembles the following:
WWW-Authenticate: Basic realm="Realm Name"

Client-side processing
After the 401 response code is returned with the authentication header, the client begins
to assemble an authorization header. Construction of this header proceeds in the following
steps:
1. The indicated Username and Password combination are concatenated into one scalar

value. The Username and Password combination are separated by a colon. If a Username value of JohnQPublic had a corresponding Password value of !*MyPa55w0rd*!,
the value sent would be the following:
JohnQPublic:!*MyPa55w0rd*!

2. The combined string literal referenced in the first step is encoded using a Base64

encoding. Assuming the concatenated value in Step 1, the encoded Base64 representation would be the following:
Sm9oblFQdWJsaWM6ISpNeVBhNTV3MHJkKiE=



Objective 4.3: Secure a Web API

CHAPTER 4

327

3. A final value is constructed by adding the authentication method, the literal Basic, and

then the encoded string specified in the previous step. The resulting value looks like
the following:
Authorization: Basic Sm9oblFQdWJsaWM6ISpNeVBhNTV3MHJkKiE=

There are two extremely noteworthy items to keep in mind. First, although the encoded
user name and password string isn’t easily readable at first glance, encoded does not mean
encrypted. Any application that monitors traffic to and from a client is also very capable of
instantaneously decoding the Base64 value. Absent encryption or hashing, the user name and
password are completely exposed to unwanted monitoring. To be blunt about it, unless the
transfer happens via HTTPS, Basic authentication is not secure. Remember for the exam that
you should never use Basic authentication without SSL.
Second, the entire authentication process is valid only within the context of a realm. If you
examine the initial response shown in the Server Side Processing section, a core component
of the header is the realm name. After the credentials are authenticated, they are valid only
within the specified realm.

Enabling SSL
To make your Basic authentication secure, you need to enable SSL. For local testing, you can
just enable SSL in ISS Express from Visual Studio by setting the SSL Enabled to True in the
properties window of Visual Studio. The value of the SSL URL can then be used for testing.
To enforce HTTPS in your Web API service, you can use the following attribute:
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response = new HttpResponseMessage
(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "HTTPS Required"
};
}
else
{
base.OnAuthorization(actionContext);
}
}
}

This attribute inspects a request and checks to see whether it is using HTTPS. If not, an error is returned to the client stating that HTTPS is required.
You can now add this attribute to specific controllers or even as a global attribute to make
sure that HTTPS is used to secure your Web API.

328

CHAPTER 4

Creating and consuming Web API-based services

When moving to a production environment, you require a certificate to enable SSL. For
testing, you can create such a certificate by using the MakeCert.exe tool.
MORE INFO  USING MAKECERT

For more information on how to use MakeCert to create certificates, see http://msdn.
microsoft.com/en-us/library/bfsktky3.aspx.

Implementing Windows Authentication
When Basic authentication is not sufficient or appropriate (for intranet applications, for
example), it might be beneficial to use Windows Authentication. Windows Authentication
enables users to be authenticated using their Windows login credentials, Kerberos, or NTLM.
Windows Authentication is easy to use from both a client and server perspective, but it has
clearly defined boundaries that might immediately exclude it as an authentication mechanism
(see Table 4-5).
TABLE 4-5  Trade-offs related to Windows Authentication

Advantage

Disadvantage

Natively supported by Internet Information Services (IIS).

Requires client-side NTLM or Kerberos support.

Client credentials are not transmitted in the request.

This is a poor choice to use for consumerfacing systems.

If the client is a member of the same domain as the
server, the client does not need to enter credentials.

This is a poor choice to use for consumerfacing systems.

Server-side processing
Very little needs to be done on the service side portion of the application. Inside the Web
API’s web.config file, the Authentication element’s Mode property should simply be set
to Windows. In your actual application, there will be several other elements inside the
element, but the following snippet shows what’s needed to enable Windows
Authentication:

   


Client-side processing
To operate correctly, requests made using the web browser necessitate browser support of
the Negotiation authentication scheme. Virtually all popular web browsers and their currently
supported versions support Negotiation, so there’s not much else needed if access will be
performed through the browser exclusively.



Objective 4.3: Secure a Web API

CHAPTER 4

329

If a .NET client application is being used instead of the browser, use of the HttpClient
class enables the use of Windows Authentication. Doing this via the HttpClient class is extremely easy, and a working example is shown here. All that’s needed is the creation of an
HttpClientHandler class. The UseDefaultCredentials property should then be set to true. This
can all be done in one operation:
HttpClientHandler ClientHandler = new HttpClientHandler
{
UseDefaultCredentials = true
};

After the HttpClientHandler instance is created, simply pass it to the constructor of the
new HttpClient instance used in your application and you are done!

Preventing cross-site request forgery
Cross-site request forgery (referred to both as CSRF and XSRF), can be a very serious problem
for developers of Web API services. XSRF is a known attack vector for many of the authentication methods used with Web API and is something that you absolutely must understand
(just like SQL injection with data access technologies) to make sure that you don’t open your
system up unsuspectingly to the wrong people. However, preventing XSRF from being a
problem is not a terribly difficult task.
Before you learn about preventing XSRF from happening, you need to understand what it
is, when it can happen, and when you need to protect against it.

What is XSRF?
Many attacks (such as SQL injection, cross-site scripting or cross-site request forgery attacks)
are well known and have been documented for a long time. SQL injection attacks have been
widely discussed in security and application development literature for many years, yet many
sites are still successfully attacked by using it because the original vulnerability was never covered or hardened. It’s understandable that a site might be vulnerable to a newly found attack,
but leaving unpatched holes around well-known vulnerabilities is a recipe for disaster and is
simply irresponsible behavior of the IT staff deploying such vulnerabilities.
There are many automated tools that enable relatively unsophisticated users to probe and
launch an attack around almost every well-known vulnerability, and there is no shortage of
people willing to employ them. Attackers span a very large profile number, including each
of the following: state-sponsored hackers, people engaged in industrial and/or sovereign
espionage, people seeking retribution with a company or entity for some perceived wrong,
“script-kiddies” (novice hackers who know enough to use an automated tool, but little more),
disgruntled former employees, and many more.
Here’s an example of an XSRF: You are logged into the Windows Azure Management
Portal and, while reading your email, you click what you think is a link for a discount for additional servers in Windows Azure, but ultimately causes your browser to navigate to https://
manage.windowsazure.com/api/DeleteAllData without your realizing it. Although this path
330

CHAPTER 4

Creating and consuming Web API-based services

doesn’t really exist in the Management Portal, such a path could exist; if it did, you could be
in some real trouble! This is one reason why such a delete via a Web API service should be
done via an HttpDelete instead of an HttpGet because clicking that link performs a GET, not
a DELETE request. Even so, you are still not immune if the link comes from another web page
that actually sends a DELETE request!
EXAM TIP

If you’re unfamiliar with XSRF, https://www.owasp.org/index.php/Cross-Site_Request_Forgery
provides a wonderful and very thorough description of it. A summary description of XSRF
is simply when a different website or application (even email links) attempts to access a resource that requires authentication to trigger some action that has some side effect.

When are you vulnerable?
It is great to know that potential vulnerabilities exist but it’s even better to know whether your
system is at risk or not to these vulnerabilities, and XSRF is no exception. XSRF can be a valid
attack vector to your system when you use an authentication mechanism in which your web
browser “keeps you logged in” or when you allow cross-site communication.
Authentication mechanisms such as Basic authentication or cookie-based authentication
are vulnerable. XSRF is about the browser implicitly trusting a URL and sending the credentials
it has stored with a request to that website.
Imagine that you are browsing a page at http://malicious.com/haha while you are waiting for a Windows Azure deployment to finish, when all of a sudden some JavaScript code
makes an attempt to send an AJAX DELETE request to https://manage.windowsazure.com/
api/DeleteAllData. Because your browser trusts windowsazure.com and has credentials for
the Management Portal, those credentials are used when the website malicious.com sends a
request to the Management Portal. If Microsoft didn’t attempt to protect the request against
XSRF, you would have a bad day!
This also means that native applications that send the user credentials on each request are
not vulnerable for XSRF attacks on your Web API. Only browser-based clients that cache user
credentials are vulnerable.

How to protect from XSRF
Okay, you know all about what XSRF is and when it is likely to be a problem. But what do you
do about it? Fortunately, this isn’t a terribly difficult problem and is something you can fairly
easily implement.
First of all, with authentication mechanisms, you are vulnerable when using systems where
you are authenticated and that state is effectively a flag. This is a problem with Basic authentication because normally the browser stores your authentication token, which is to avoid
prompting the user for authentication on each request. This stored token can be used by
other sites to execute requests on your service.



Objective 4.3: Secure a Web API

CHAPTER 4

331

Protecting an MVC web application is fairly easy using the Html.AntiForgeryToken method
in a view and the ValidateAntiForgeryToken attribute on your action method.
An AntiForgeryToken is a hidden field in your HTML that looks something like this:
value="saTFWpkKN0BYazFtN6c4YbZAmsEwG0srqlUqqloi/fVgeV2ciIFVmelvzwRZpArs" />

This value is randomly generated and is also stored in a cookie that is passed to the user.
The attribute on your action method makes sure that the value in the cookie and the one
submitted in the form data from the client match. This is secure because a potential attacker
won’t know the value from the cookie and can’t use this value to forge a valid form post.
For Web API, you can follow the same principle. You want to send a value both in a cookie
and in the generated HTML. Then on the server, you compare these two values and make
sure that they match. You can send the antiforgery token by using the Html.AntiForgeryToken
in your HTML.
Checking the token in your Web API controller takes a couple of steps:
1. Get the token from the cookie.
2. Get the token from the form data.
3. Pass the values for the token from both sources to AntiForgery.Validate.
4. Send an Unauthorized when validation fails.
5. In code, this process would look something like this:
CookieHeaderValue cookie = Request.Headers
.GetCookies(AntiForgeryConfig.CookieName)
.FirstOrDefault();
if (cookie == null) return;
Stream requestBufferedStream = Request.Content.ReadAsStreamAsync().Result;
requestBufferedStream.Position = 0;
NameValueCollection myform = Request.Content.ReadAsFormDataAsync().Result;
try
{
AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
myform[AntiForgeryConfig.CookieName]);
}
catch
{
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.Unauthorized));
}

Of course, you can move this code to an attribute or handler to avoid repeating it in each
action method.
There are some nuances to this solution if your client is not an MVC application or if you
have the content type set to Streaming instead of the default Buffered setting. With other

332

CHAPTER 4

Creating and consuming Web API-based services

client types, you need an equivalent to the Html.AntiForgeryToken method to persist the
tokens and send them back to the server. The solution to the content type setting is an even
more complicated topic that is beyond the scope of this book and the exam, but you are
encouraged to investigate that topic on your own.
EXAM TIP

Of course, it’s not required to memorize all the code involved in checking the antiforgery
tokens. You do need to make sure that you understand the ideas behind it. Sending two
values, one in a cookie and one in the form data, and making sure these values match is the
basic idea for protecting against XSRF. A good practice is to create a simple Web API that is
vulnerable to XSRF and then protecting it so you understand all steps involved. An example
can be found at http://www.dotnetcurry.com/ShowArticle.aspx?ID=890.

Enabling cross-domain requests
For security reasons, browsers prohibit AJAX calls to resources residing outside the current
origin. This is called the same-origin policy. The origin refers to the URL of your website.
For example, if your website is located at http://www.contoso.com, you can’t make an AJAX
request to http://www.someotherwebsite.com. You also can’t make request to your domain
with another protocol (such as https instead of http) or on a different port (81 instead of 80
for example).
With websites storing authentication data in cookies that are maintained by the browser,
this policy is important for avoiding XSRF attacks.
But sometimes you want to explicitly allow a cross-domain AJAX call. This is called crossorigin resource sharing (CORS).
A simple way to allow CORS is to add a setting to your web.config file:









This code adds a special header to your request, stating that CORS is allowed. However,
this will allow CORS for all your Web API services and for all clients that are calling it, so this is
not the most secure way to enable CORS. Microsoft created a NuGet package to allow CORS
in Web API on a more granular level.
You can install this package by executing the following line in your Package Manager
Console (the -pre flag is required at this time because the package is still prerelease):
Install-Package Microsoft.AspNet.WebApi.Cors -pre



Objective 4.3: Secure a Web API

CHAPTER 4

333

You now get a new extension method on your HttpConfiguration object:
config.EnableCors();

By calling this method in the static WebApiConfig.Register method, you enable CORS
support for your application. Now you can add attributes to controllers or action methods to
enable CORS like this:
public class ValuesController : ApiController
{
[EnableCors(origins: "http://localhost:26891", headers: "*", methods: "*")]
public IEnumerable Get()
{
return new[] { "Value1", "Value2" };
}
}

In this case, you allow CORS access to the Get method from http://localhost:26891. All
other domains don’t have access. You can also apply this attribute to a complete controller or
even globally to your Web API configuration.

Implementing and extending authorization filters
Web API offers the default AuthorizeAttribute to make sure that all users who access your
controller or action method are authenticated. After applying this attribute, users who are not
authenticated can’t access your service.
If the default behavior of the AuthorizeAttribute is not sufficient, you have a couple of options for extending it:
■■

■■

■■

AuthorizeAttribute  Extend this class to perform authorization logic based on the
current user and the user’s roles.
AuthorizationFilterAttribute  Extend this class to perform synchronous authorization logic that is not necessarily based on the current user or role.
IAuthorizationFilter  Implement this interface to perform asynchronous authorization logic—for example, if your authorization logic makes asynchronous I/O or
network calls. (If your authorization logic is CPU-bound, it is simpler to derive from
AuthorizationFilterAttribute because then you don’t need to write an asynchronous
method.)

The default AuthorizeAttribute works on white listing, meaning that you pass it the roles
for users and roles that are explicitly allowed to access a resource. You can create a custom
attribute for black listing, as Listing 4-2 shows.

334

CHAPTER 4

Creating and consuming Web API-based services

LISTING 4-2  A BlackListAuthorizationAttribute
public class BlackListAuthorizationAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
IPrincipal user = Thread.CurrentPrincipal;
if (user == null) return true;
var splitUsers = SplitString(Users);
if (splitUsers.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
return false;
var splitRoles = SplitString(Roles);
if (splitRoles.Any(user.IsInRole)) return false;
return true;
}
private static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}

By overriding the IsAuthorized method, you can implement your own custom authorization logic.
Deriving from IAuthorizationFilter or the AuthorizationFilterAttribute is also possible, but
is also more difficult. If you need to implement some basic authorization logic on the exam,
you can almost always derive from AuthorizeAttribute and override the IsAuthorized method.
Only when you have authorization logic that’s not based on the current user or role should
you look at the other classes.



Objective 4.3: Secure a Web API

CHAPTER 4

335