© Prabath Siriwardena 2020
P. SiriwardenaAdvanced API Securityhttps://doi.org/10.1007/978-1-4842-2050-4_21

Basic/Digest Authentication

Prabath Siriwardena1 
(1)
San Jose, CA, USA
 

HTTP Basic authentication and Digest authentication are two authentication schemes, used for protecting resources on the Web. Both are based on username- and password-based credentials. When trying to log in to a web site, if the browser presents you a dialog box asking your username and password, then most probably this web site is protected with HTTP Basic or Digest authentication. Asking the browser to challenge the user to authenticate is one of the quick and dirty ways of protecting a web site. None or at least very few web sites on the Internet today use HTTP Basic or Digest authentication. Instead, they use a nice form-based authentication or their own custom authentication schemes. But still some use HTTP Basic/Digest authentication to secure direct API-level access to resources on the Web.

HTTP Basic authentication is first standardized through the HTTP/1.0 RFC (Request For Comments)1 by IETF (Internet Engineering Task Force). It takes the username and password over the network as an HTTP header in cleartext. Passing user credentials over the wire in cleartext is not secure, unless it’s used over a secured transport channel, like HTTP over TLS (Transport Layer Security). This limitation was addressed in the RFC 2617, which defined two authentication schemes for HTTP: Basic Access Authentication and Digest Access Authentication. Unlike Basic authentication, the Digest authentication is based on cryptographic hashes and never sends user credentials over the wire in cleartext.

HTTP Basic Authentication

The HTTP/1.0 specification first defined the scheme for HTTP Basic authentication and got further refined by RFC 2617. The RFC 2617 was proposed as a companion to the HTTP 1.1 specification or the RFC 2616.2 Then again in 2015, the RFC 2617 was obsoleted by the new RFC 7617. It’s a challenge-response-based authentication scheme, where the server challenges the user to provide valid credentials to access a protected resource. With this model, the user has to authenticate him for each realm. The realm can be considered as a protection domain. A realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme and/or authorization database.3 A given user can belong to multiple realms simultaneously. The value of the realm is shown to the user at the time of authentication—it’s part of the authentication challenge sent by the server. The realm value is a string, which is assigned by the authentication server. Once the request hits the server with Basic authentication credentials, the server will authenticate the request only if it can validate the username and the password, for the protected resource, against the corresponding realm.

Accessing the GitHub API with HTTP Basic Authentication

GitHub is a web-based git repository hosting service. Its REST API4 is protected with HTTP Basic authentication. This exercise shows you how to access the secured GitHub API to create a git repository. You need to have a GitHub account to try out the following, and in case you do not have one, you can create an account from https://github.com.

Let’s try to invoke the following GitHub API with cURL. It’s an open API that doesn’t require any authentication and returns pointers to all available resources, corresponding to the provided GitHub username.
> curl  -v  https://api.github.com/users/{github-user}
For example:
> curl  -v  https://api.github.com/users/prabath
The preceding command returns back the following JSON response.
{
   "login":"prabath",
   "id":1422563,
   "avatar_url":"https://avatars.githubusercontent.com/u/1422563?v=3",
   "gravatar_id":"",
   "url":"https://api.github.com/users/prabath",
   "html_url":"https://github.com/prabath",
   "followers_url":"https://api.github.com/users/prabath/followers",
   "following_url":"https://api.github.com/users/prabath/following{/other_user}",
   "gists_url":"https://api.github.com/users/prabath/gists{/gist_id}",
   "starred_url":"https://api.github.com/users/prabath/starred{/owner}{/repo}",
   "subscriptions_url":"https://api.github.com/users/prabath/subscriptions",
   "organizations_url":"https://api.github.com/users/prabath/orgs",
   "repos_url":"https://api.github.com/users/prabath/repos",
   "events_url":"https://api.github.com/users/prabath/events{/privacy}",
   "received_events_url":"https://api.github.com/users/prabath/received_events",
   "type":"User",
   "site_admin":false,
   "name":"Prabath Siriwardena",
   "company":"WSO2",
   "blog":"http://blog.faciellogin.com",
   "location":"San Jose, CA, USA",
   "email":"[email protected]",
   "hireable":null,
   "bio":null,
   "public_repos":3,
   "public_gists":1,
   "followers":0,
   "following":0,
   "created_at":"2012-02-09T10:18:26Z",
   "updated_at":"2015-11-23T12:57:36Z"
}

Note All the cURL commands used in this book are broken into multiple lines just for clarity. When you execute them, make sure to have it as a single line, with no line breaks.

Now let’s try out another API. Here you create a GitHub repository with the following API call. This returns a negative response with the HTTP status code 401 Unauthorized. The API is secured with HTTP Basic authentication, and you need to provide credentials to access it:
> curl -i  -X POST -H 'Content-Type: application/x-www-form-urlencoded'
       -d '{"name": "my_github_repo"}'  https://api.github.com/user/repos
The preceding command returns back the following HTTP response, indicating that the request is not authenticated. Observing the response from GitHub for the unauthenticated API call to create a repository, it looks as though the GitHub API isn’t fully compliant with the HTTP 1.1 specification. According to the HTTP 1.1 specification, whenever the server returns a 401 status code, it also must return the HTTP header WWW-Authenticate.
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Content-Length: 115
Server: GitHub.com
Status: 401 Unauthorized
{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3/repos/#create"
}
Let’s invoke the same API with proper GitHub credentials. Replace $GitHubUserName and $GitHubPassword with your credentials:
curl  -i –v -u $GitHubUserName:$GitHubPassword
        -X POST -H 'Content-Type: application/x-www-form-urlencoded'
        -d '{"name": "my_github_repo"}'  https://api.github.com/user/repos
Next, let’s look at the HTTP request generated from the cURL client:
POST /user/repos HTTP/1.1
Authorization: Basic cHJhYmF0aDpwcmFiYXRoMTIz

The HTTP Authorization header in the request is generated from the username and password you provided. The formula is simple: Basic Base64Encode(username:password). Any base64-encoded text is no better than cleartext—it can be decoded quite easily back to the cleartext. That is why Basic authentication on plain HTTP isn’t secured. It must be used in conjunction with a secured transport channel, like HTTPS.

The preceding command returns back the following HTTP response (truncated for clarity), indicating that the git repository was created successfully.
HTTP/1.1 201 Created
Server: GitHub.com
Content-Type: application/json; charset=utf-8
Content-Length: 5261
Status: 201 Created
{
  "id": 47273092,
  "name": "my_github_repo",
  "full_name": "prabath/my_github_repo"
}

Note

To add HTTP Basic authentication credentials to a request generated from a cURL client, you can use the option –u username:password. This creates the base64-encoded HTTP basic authorization header. –i is used to include HTTP headers in the output, and –v is used to run cURL in verbose mode. –H is used to set HTTP headers in the outgoing request, and –d is used to post data to the endpoint.

HTTP Digest Authentication

HTTP Digest authentication was initially proposed by the RFC 20695 as an extension to the HTTP/1.0 specification to overcome certain limitations in HTTP Basic authentication. Later this specification was made obsolete by the RFC 2617. The RFC 2617 removed some optional elements specified by the RFC 2069 due to problems found since its publication and introduced a set of new elements for compatibility, and those new elements have been made optional. Digest authentication is an authentication scheme based on a challenge-response model, which never sends the user credentials over the wire. Because the credentials are never sent over the wire with the request, Transport Layer Security (TLS) isn’t a must. Anyone intercepting the traffic won’t be able to discover the password in cleartext.

To initiate Digest authentication, the client has to send a request to the protected resource with no authentication information, which results in a challenge (in the response). The following example shows how to initiate a Digest authentication handshake from cURL (this is just an example, don’t try it till we set up the cute-cupcake sample later in this appendix):
> curl -k –-digest –u userName:password -v https://localhost:8443/recipe

Note

To add HTTP Digest authentication credentials to a request generated from a cURL client, use the option –-digest –u username: password.

Let’s look at the HTTP headers in the response. The first response is a 4016 with the HTTP header WWW-Authenticate, which in fact is the challenge:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm="cute-cupcakes.com", qop="auth",
nonce="1390781967182:c2db4ebb26207f6ed38bb08eeffc7422",
opaque="F5288F4526B8EAFFC4AC79F04CA8A6ED"

Note

You learn more about the Recipe API and how to deploy it locally as you proceed through this appendix. The “Securing the Recipe API with HTTP Digest Authentication” exercise at the end of the appendix explains how to secure an API with Digest authentication.

The challenge from the server consists of the following key elements. Each of these elements is defined in the RFC 2617:
  • realm: A string to be displayed to users so they know which username and password to use. This string should contain at least the name of the host performing the authentication and may additionally indicate the collection of users who may have access.

  • domain: This is an optional element, not present in the preceding response. It’s a comma-separated list of URIs. The intent is that the client could use this information to know the set of URIs for which the same authentication information should be sent. The URIs in this list may exist on different servers. If this keyword is omitted or empty, the client should assume that the domain consists of all URIs on the responding server.

  • nonce: A server-specified data string, which should be uniquely generated each time a 401 response is made. The value of the nonce is implementation dependent and is opaque to the client. The client should not try to interpret the value of nonce.

  • opaque: A string of data, specified by the server, that should be returned by the client unchanged in the Authorization header of subsequent requests with URIs in the same protection space (which is the realm). Because the client is returning back the value of the opaque element given to it by the server for the duration of a session, the opaque data can be used to transport authentication session state information or can be used as a session identifier.

  • stale: A flag, indicating that the previous request from the client was rejected because the nonce value was stale. If stale is TRUE (case insensitive), the client may wish to simply retry the request with a new nonce value, without reprompting the user for a new username and password. The server should only set stale to TRUE if it receives a request for which the nonce is invalid but with a valid digest for that nonce (indicating that the client knows the correct username/password). If stale is FALSE, or anything other than TRUE, or the stale directive is not present, the username and/or password are invalid, and new values must be obtained. This flag is not shown in the preceding response.

  • algorithm: This is an optional element, not shown in the preceding response. The value of algorithm is a string indicating a pair of algorithms used to produce the digest and a checksum. If the client does not understand the algorithm, the challenge should be ignored, and if it is not present, it is assumed to be MD5.

  • qop: The quality of protection options applied to the response by the server. The value auth indicates authentication; while the value auth-int indicates authentication with integrity protection. This is an optional element and introduced to be backward compatible with the RFC 2069.

Once the client gets the response from the server, it has to respond back. Here’s the HTTP request with the response to the challenge:
Authorization: Digest username="prabath", realm="cute-cupcakes.com",
nonce="1390781967182:c2db4ebb26207f6ed38bb08eeffc7422", uri="/recipe", cnonce="MTM5MDc4", nc=00000001, qop="auth",
response="f5bfb64ba8596d1b9ad1514702f5a062",
opaque="F5288F4526B8EAFFC4AC79F04CA8A6ED"
The following are the key elements in the response from the client:
  • username: The unique identifier of the user who’s going to invoke the API.

  • realm/qop/nonce/opaque: The same as in the initial challenge from the server. The value of qop indicates what quality of protection the client has applied to the message. If present, its value MUST be one of the alternatives the server indicated it supports in the WWW-Authenticate header.

  • cnonce: This MUST be specified if a qop directive is sent and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field. The value of cnonce is an opaque quoted string value provided by the client and used by both the client and the server to avoid chosen plaintext attacks,7 to provide mutual authentication, and to provide some message integrity protection. This is not shown in the preceding response.

  • nc: This MUST be specified if a qop directive is sent and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field. The value of nc is the hexadecimal count of the number of requests (including the current request) that the client has sent with the same nonce value. For example, in the first request sent in response to a given nonce value, the client sends "nc=00000001". The purpose of this directive is to allow the server to detect request replays by maintaining its own copy of this count—if the same nc value is seen twice for the same nonce value, then the request is a replay.

  • digest-uri: The request URI from the request line. Duplicated here because proxies are allowed to change the Request-Line in transit. The value of the digest-uri is used to calculate the value of the response element, as explained later in the chapter.

  • auth-param: This is an optional element not shown in the preceding response. It allows for future extensions. The server MUST ignore any unrecognized directive.

  • response: The response to the challenge sent by the server, calculated by the client. The following section explains how the value of response is calculated.

The value of response is calculated in the following manner. Digest authentication supports multiple algorithms. RFC 2617 recommends using MD5 or MD5-sess (MD5-session). If no algorithm is specified in the server challenge, MD5 is used. Digest calculation is done with two types of data: security-related data (A1) and message-related data (A2). If you use MD5 as the hashing algorithm or if it is not specified, then you define security-related data (A1) in the following manner:
A1 = username:realm:password
If you use MD5-sess as the hashing algorithm, then you define security-related data (A1) in the following manner. cnonce is an opaque quoted string value provided by the client and used by both the client and the server to avoid chosen plaintext attacks. The value of nonce is the same as in the server challenge. If the MD5-sess is picked as the hashing algorithm, then A1 is calculated only once on the first request by the client following receipt of a WWW-Authenticate challenge from the server:
A1 = MD5 (username:realm:password):nonce:cnonce
RFC 2617 defines message-related data (A2) in two ways, based on the value of qop in the server challenge. If the value is auth or undefined, then the message-related data (A2) is defined in the following manner. The value of the request-method element can be GET, POST, PUT, DELETE, or any HTTP verb, and the value of the uri-directive-value element is the request URI from the request line:
A2 = request-method:uri-directive-value
If the value of qop is auth-int, then you need to protect the integrity of the message, in addition to authenticating. A2 is derived in the following manner. When you have MD5 or MD5-sess as the hashing algorithm, the value of H is MD5:
A2 = request-method:uri-directive-value:H(request-entity-body)
The final value of the digest is calculated in the following way, based on the value of qop. If qop is set to auth or auth-int, then the final digest value is as shown next. The nc value is the hexadecimal count of the number of requests (including the current request) that the client has sent with the nonce value in this request. This directive helps the server detect replay attacks. The server maintains its own copy of nonce and the nonce count (nc); if any are seen twice, that indicates a possible replay attack:
MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2))
If qop is undefined, then the final digest value is
MD5(MD5(A1):<nonce>:MD5(A2))
This final digest value will be set as the value of the response element in the HTTP request from the client to the server. Once the client responds back to the server’s initial challenge, the subsequent requests from there onward do not need all the preceding three message flows (the initial unauthenticated request from the client, the challenge from the server, and the response to the challenge from the client). The server will send a challenge to the client only if there is no valid authorization header in the request. Once the client gets the initial challenge, for the subsequent requests, the same parameters from the challenge will be used. In other words, the response by the client to a WWW-Authenticate challenge from the server for a protection space starts an authentication session with that protection space. The authentication session lasts until the client receives another WWW-Authenticate challenge from any server in the protection space. The client should remember the username, password, nonce, nonce count, and opaque values associated with the authentication session to use to construct the authorization header in the subsequent requests within that protection space. For example, the authorization header from the client should have the nonce value in each request. This nonce value is picked from the initial challenge from the server, but the value of the nc element will be increased by one, for each request. Table F-1 provides a comparison between HTTP Basic authentication and Digest authentication.
Table F-1

HTTP Basic Authentication vs. HTTP Digest Authentication

HTTP Basic Authentication

HTTP Digest Authentication

Sends credentials in cleartext over the wire.

Credentials are never sent in cleartext. A digest derived from the cleartext password is sent over the wire.

Should be used in conjunction with a secured transport channel, like HTTPS.

Doesn’t depend on the security of the underneath transport channel.

Only performs authentication.

Can be used to protect the integrity of the message, in addition to authentication (with qop=auth-int).

User store can store passwords as a salted hash.

User store should store passwords in cleartext or should store the hash value of username:realm: password.

Note

With HTTP Digest authentication, a user store has to store passwords either in cleartext or as the hashed value of username:password:realm. This is required because the server has to validate the digest sent from the client, which is derived from the cleartext password (or the hash of username:realm:password).

Cute-Cupcake Factory: Deploying the Recipe API in Apache Tomcat

In this example, you deploy a prebuilt web application with the Recipe API in Apache Tomcat. The Recipe API is hosted and maintained by the Cute-Cupcake factory. It’s a public API with which the customers of Cute-Cupcake factory can interact. The Recipe API supports the following five operations:
  • GET /recipe: Returns all the recipes in the system

  • GET /recipe/{$recipeNo}: Returns the recipe with the given recipe number

  • POST /recipe: Creates a new recipe in the system

  • PUT /recipe: Updates the recipe in the system with the given details

  • DELETE /recipe/{$recipeNo}: Deletes the recipe from the system with the provided recipe number

You can download the latest version of Apache Tomcat from http://tomcat.apache.org. All the examples discussed in this book use Tomcat 9.0.20.

To deploy the API, download the recipe.war file from https://github.com/apisecurity/samples/blob/master/appendix-f/recipe.war and copy it to [TOMCAT_HOME]webapps. To start Tomcat, run the following from the [TOMCAT_HOME]in directory:
[Linux] sh catalina.sh run
[Windows] catalina.bat run
Once the server is started, use cURL to execute the following command. Here it’s assumed that Tomcat is running on its default HTTP port 8080:
> curl  http://localhost:8080/recipe
This returns all the recipes in the system as a JSON payload:
{
   "recipes":[
      {
         "recipeId":"10001",
         "name":"Lemon Cupcake",
         "ingredients":"lemon zest, white sugar,unsalted butter, flour,salt, milk",
         "directions":"Preheat oven to 375 degrees F (190 degrees C). Line 30 cupcake pan cups with paper liners...."
      },
      {
         "recipeId":"10002",
         "name":"Red Velvet Cupcake",
         "ingredients":"cocoa powder, eggs, white sugar,unsalted butter, flour,salt, milk",
         "directions":" Preheat oven to 350 degrees F. Mix flour, cocoa powder,
                                 baking soda and salt in medium bowl. Set aside...."
      }
   ]
}
To get the recipe of any given cupcake, use the following cURL command, where 10001 is the ID of the cupcake you just created:
> curl  http://localhost:8080/recipe/10001
This returns the following JSON response:
{
         "recipeId":"10001",
         "name":"Lemon Cupcake",
         "ingredients":"lemon zest, white sugar,unsalted butter, flour,salt, milk",
         "directions":"Preheat oven to 375 degrees F (190 degrees C). Line 30 cupcake pan cups with paper liners...."
}
To create a new recipe, use the following cURL command:
curl  -X POST -H 'Content-Type: application/json'
        -d '{"name":"Peanut Butter Cupcake",
             "ingredients":"peanut butter, eggs, sugar,unsalted butter, flour,salt, milk",
             "directions":"Preheat the oven to 350 degrees F (175 degrees C).
              Line a cupcake pan with paper liners, or grease and flour cups..."
             }' http://localhost:8080/recipe
This returns the following JSON response:
{
         "recipeId":"10003",
         "location":"http://localhost:8080/recipe/10003",
}
To update an existing recipe, use the following cURL command:
curl  -X PUT -H 'Content-Type: application/json'
        -d '{"name":"Peanut Butter Cupcake",
             "ingredients":"peanut butter, eggs, sugar,unsalted butter, flour,salt, milk",
             "directions":"Preheat the oven to 350 degrees F (175 degrees C). Line a cupcake pan with
              paper liners, or grease and flour cups..."
             }' http://localhost:8080/recipe/10003
This returns the following JSON response:
{
         "recipeId":"10003",
         "location":"http://localhost:8080/recipe/10003",
}
To delete an existing recipe, use the following cURL command:
> curl  -X DELETE http://localhost:8080/recipe/10001

Note

To do remote debugging with Apache Tomcat, start the server under Linux operating system as sh catalina.sh jpda run or under Windows operating system as catalina.bat jpda run. This opens port 8000 for remote debugging connections.

Configuring Apache Directory Server (LDAP)

Apache Directory Server is an open source LDAP server distributed under Apache 2.0 license. You can download the latest version from http://directory.apache.org/studio/. It’s recommended that you download the Apache Directory Studio8 itself, as it comes with a set of very useful tools to configure LDAP. We use Apache Directory Studio 2.0.0 in the following example.

The following steps are needed only if you don’t have an LDAP server set up to run. First you need to start Apache Directory Studio. This provides a management console to create and manage LDAP servers and connections. Then proceed with the following steps:
  1. 1.

    From Apache Directory Studio, go to the LDAP Servers view. If it’s not there already, go to Window ➤ Show View ➤ LDAP Servers.

     
  2. 2.

    Right-click LDAP Servers View, choose New ➤ New Server, and select ApacheDS 2.0.0. Give any name to the server in the Server Name text box, and click Finish.

     
  3. 3.

    The server you created appears in the LDAP Servers view. Right-click the server, and select Run. If it’s started properly, State is updated to Started.

     
  4. 4.

    To view or edit the configuration of the server, right-click it and select Open Configuration. By default, the server starts on LDAP port 10389 and LDAPS port 10696.

     
Now you have an LDAP server up and running. Before you proceed any further, let’s create a test connection to it from the Apache Directory Studio:
  1. 1.

    From Apache Directory Studio, get to the Connections view. If it’s not there already, go to Window ➤ Show View ➤ Connections.

     
  2. 2.

    Right-click Connections View, and select New Connection.

     
  3. 3.

    In the Connection Name text box, give a name to the connection.

     
  4. 4.

    The Host Name field should point to the server where you started the LDAP server. In this case, it’s localhost.

     
  5. 5.

    The Port field should point to the port of your LDAP server, which is 10389 in this case.

     
  6. 6.

    Keep Encryption Method set to No Encryption for the time being. Click Next.

     
  7. 7.

    Type uid=admin,ou=system as the Bind DN and secret as the Bind Password, and click Finish. These are the default Bind DN and password values for Apache Directory Server.

     
  8. 8.

    The connection you just created appears in the Connections view. Double-click it, and the data retrieved from the underlying LDAP server appears in the LDAP Browser view.

     
In the sections that follow, you need some users and groups in the LDAP server. Let’s create a user and a group. First you need to create an organizational unit (OU) structure under the dc=example,dc=com domain in Apache Directory Server:
  1. 1.

    In Apache Directory Studio, get to the LDAP browser by clicking the appropriate LDAP connection in the Connections view.

     
  2. 2.

    Right-click dc=example,dc=com, and choose New ➤ New Entry ➤ Create Entry From Scratch. Pick organizationalUnit from Available Object Classes, click Add, and then click Next. Select ou for the RDN, and give it the value groups. Click Next and then Finish.

     
  3. 3.

    Right-click dc=example,dc=com, and choose New ➤ New Entry ➤ Create Entry From Scratch. Pick organizationalUnit from Available Object Class, click Add, and then click Next. Select ou for the RDN, and give it the value users. Click Next and then Finish.

     
  4. 4.

    Right-click dc=example,dc=com/ou=users, and choose New ➤ New Entry ➤ Create Entry From Scratch. Pick inetOrgPerson from Available Object Class, click Add, and then click Next. Select uid for the RDN, give it a value, and click Next. Complete the empty fields with appropriate values. Right-click the same pane, and choose New Attribute. Select userPassword as the Attribute Type, and click Finish. Enter a password, select SSHA-256 as the hashing method, and click OK.

     
  5. 5.

    The user you created appears under dc=example,dc=com/ou=users in the LDAP browser.

     
  6. 6.

    To create a group, right-click dc=example,dc=com/ou=groups ➤ New ➤ New Entry ➤ Create Entry From Scratch. Pick groupOfUniqueNames from Available Object Class, click Add, and click Next. Select cn for the RDN, give it a value, and click Next. Give the DN of the user created in the previous step as the uniqueMember (e.g., uid=prabath,ou=users,ou=system), and click Finish.

     
  7. 7.

    The group you created appears under dc=example,dc=com/ou=groups in the LDAP browser.

     

Connecting Apache Tomcat to Apache Directory Server (LDAP)

You’ve already deployed the Recipe API in Apache Tomcat. Let’s see how you can configure Apache Tomcat to talk to the LDAP server you configured, following these steps:
  1. 1.

    Shut down the Tomcat server if it’s running.

     
  2. 2.

    By default, Tomcat finds users from the conf/tomcat-users.xml file via org.apache.catalina.realm.UserDatabaseRealm.

     
  3. 3.
    Open [TOMCAT_HOME]confserver.xml, and comment out the following line in it:
    <Resource
            name="UserDatabase" auth="Container"
            type="org.apache.catalina.UserDatabase"
            description="User database that can be updated and saved"
            factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
            pathname="conf/tomcat-users.xml" />
     
  4. 4.
    In [TOMCAT_HOME]confserver.xml, comment out the following line, which points to the UserDatabaseRealm:
    <Realm  className="org.apache.catalina.realm.UserDatabaseRealm"
            resourceName="UserDatabase"/>
     
  5. 5.
    To connect to the LDAP server, you should use the JNDIRealm. Copy and paste the following configuration into [TOMCAT_HOME]confserver.xml just after <Realm className="org.apache.catalina.realm.LockOutRealm">:
    <Realm  className="org.apache.catalina.realm.JNDIRealm"
            debug="99"
            connectionURL="ldap://localhost:10389"
            roleBase="ou=groups , dc=example, dc=com"
            roleSearch="(uniqueMember={0})"
            roleName="cn"
            userBase="ou=users, dc=example, dc=com"
            userSearch="(uid={0})"/>
     

Securing an API With HTTP Basic Authentication

The Recipe API that you deployed in Apache Tomcat is still an open API. Let’s see how to secure it with HTTP Basic authentication. You want to authenticate users against the corporate LDAP server and also use access control based on HTTP operations (GET, POST, DELETE, PUT). The following steps guide you on how to secure the Recipe API with HTTP Basic authentication:
  1. 1.

    Shut down the Tomcat server if it’s running, and make sure connectivity to the LDAP server works correctly.

     
  2. 2.
    Open [TOMCAT_HOME]webapps ecipeWEB-INFweb.xml and add the following under the root element <web-app>. The security-role element at the bottom of the following configuration lists all the roles allowed to use this web application:
     <security-constraint>
      <web-resource-collection>
              <web-resource-name>Secured Recipe API</web-resource-name>
              <url-pattern>/∗</url-pattern>
      </web-resource-collection>
      <auth-constraint>
              <role-name>admin</role-name>
      </auth-constraint>
     </security-constraint>
     <login-config>
              <auth-method>BASIC</auth-method>
              <realm-name>cute-cupcakes.com</realm-name>
     </login-config>
     <security-role>
              <role-name>admin</role-name>
     </security-role>

    This configuration will protect the complete Recipe API from unauthenticated access attempts. A legitimate user should have an account in the corporate LDAP server and also should be in the admin group. If you don’t have a group called admin, change the preceding configuration appropriately.

     
  3. 3.
    You can further enable fine-grained access control to the Recipe API by HTTP operation. You need to have a <security-constraint> element defined for each scenario. The following two configuration blocks will let any user that belongs to the admin group perform GET/POST/PUT/DELETE on the Recipe API, whereas a user that belongs to the user group can only do a GET . When you define an http-method inside a web-resource-collection element, only those methods are protected. The rest can be invoked by anyone if no other security constraint has any restrictions on those methods. For example, if you only had the second block, then any user would be able to do a POST. Having the first block that controls POST will allow only the legitimate user to do a POST to the Recipe API. The security-role element at the bottom of the following configuration lists all the roles allowed to use this web application:
    <security-constraint>
        <web-resource-collection>
               <web-resource-name>Secured Recipe API</web-resource-name>
               <url-pattern>/∗</url-pattern>
               <http-method>GET</http-method>
               <http-method>PUT</http-method>
               <http-method>POST</http-method>
               <http-method>DELETE</http-method>
        </web-resource-collection>
        <auth-constraint>
               <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <web-resource-collection>
               <web-resource-name>Secured Recipe API</web-resource-name>
               <url-pattern>/∗</url-pattern>
               <http-method>GET</http-method>
               </web-resource-collection>
        <auth-constraint>
               <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
               <auth-method>BASIC</auth-method>
               <realm-name>cute-cupcakes.com</realm-name>
    </login-config>
    <security-role>
               <role-name>admin</role-name>
               <role-name>user</role-name>
    </security-role>
     

Enabling TLS in Apache Tomcat

The way you configured HTTP Basic authentication in the previous exercise isn’t secure enough. It uses HTTP to transfer credentials. Anyone who can intercept the channel can see the credentials in cleartext. Let’s see how to enable Transport Layer Security (TLS) in Apache Tomcat and restrict access to the Recipe API only via TLS:
  1. 1.

    To enable TLS, first you need to have a keystore with a public/private key pair. You can create a keystore using Java keytool. It comes with the JDK distribution, and you can find it in [JAVA_HOME]in. The following command creates a Java keystore with the name catalina-keystore.jks. This command uses catalina123 as the keystore password as well as the private key password.

     
NoteJAVA_HOME refers to the directory where you’ve installed the JDK. To run the keytool, you need to have Java installed in your system.
> keytool   -genkey -alias localhost -keyalg RSA -keysize 1024
             -dname "CN=localhost"
             -keypass catalina123
             -keystore catalina-keystore.jks
             -storepass catalina123
  1. 2.
    Copy catalina-keystore.jks to [TOMCAT_HOME]conf, and add the following element to [TOMCAT_HOME]confserver.xml under the <Service> parent element. Replace the values of keyStoreFile and keystorePass elements appropriately:
    <Connector
            port="8443"
            maxThreads="200"
            scheme="https"
            secure="true"
            SSLEnabled="true"
            keystoreFile="absolute/path/to/catalina-keystore.jks"
            keystorePass="catalina123"
            clientAuth="false"
            sslProtocol="TLS"/>
     
  2. 3.

    Start the Tomcat server, and execute the following cURL command to validate the TLS connectivity. Make sure you replace the values of username and password appropriately. They must come from the underlying user store:

    > curl -k  -u username:password  https://localhost:8443/recipe

    You’ve configured Apache Tomcat to work with TLS. Next you need to make sure that the Recipe API only accepts connections over TLS.

    Open [TOMCAT_HOME]webapps ecipeWEB-INFweb.xml, and add the following under each <security-constraint> element. This makes sure only TLS connections are accepted:
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
     

Securing the Recipe API with HTTP Digest Authentication

The Tomcat JNDIRealm that you used previously to connect to the LDAP server doesn’t support HTTP Digest authentication. If you need HTTP Digest authentication support, you have to write your own Realm, extending Tomcat JNDIRealm, and override the getPassword() method. To see how to secure an API with Digest authentication, we need to switch back to the Tomcat UserDatabaseRealm:
  1. 1.
    Open [TOMCAT_HOME]confserver.xml, and make sure that the following line is there. If you commented this out during a previous exercise, revert it back:
    <Resource
            name="UserDatabase"
            auth="Container"
            type="org.apache.catalina.UserDatabase"
            description="User database that can be updated and saved"
            factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
            pathname="conf/tomcat-users.xml" />
     
  2. 2.
    In [TOMCAT_HOME]confserver.xml, make sure that the following line, which points to UserDatabaseRealm, is there. If you commented it out during a previous exercise, revert it back:
    <Realm  className="org.apache.catalina.realm.UserDatabaseRealm"
            resourceName="UserDatabase"/>
     
  3. 3.
    Open [TOMCAT_HOME]webapps ecipeWEB-INFweb.xml, and add the following under the root element <web-app>:
    <security-constraint>
      <web-resource-collection>
                   <web-resource-name>Secured Recipe API</web-resource-name>
                   <url-pattern>/∗ </url-pattern>
      </web-resource-collection>
      <auth-constraint>
                   <role-name>admin</role-name>
      </auth-constraint>
    </security-constraint>
    <login-config>
                   <auth-method>DIGEST</auth-method>
                   <realm-name>cute-cupcakes.com</realm-name>
    </login-config>
    <security-role>
                   <role-name>admin</role-name>
    </security-role>
     
  4. 4.
    Open [TOMCAT_HOME]conf omcat-users.xml, and add the following under the root element. This adds a role and a user to Tomcat’s default file system–based user store:
     <role rolename="admin"/>
     <user username="prabath" password="prabath123" roles="admin"/>
     
  5. 5.

    Invoke the API with the cURL command shown next. The --digest -u username:password option used here generates the password in digest mode and adds it to the HTTP request. Replace username:password with appropriate values:

    > curl -k -v --digest -u username:password https://localhost:8443/recipe
     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.172.249