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.
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.
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.
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.
Note
To add HTTP Digest authentication credentials to a request generated from a cURL client, use the option –-digest –u username: password.
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.
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.
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.
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
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.
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.
- 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.
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.
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.
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.
- 1.
From Apache Directory Studio, get to the Connections view. If it’s not there already, go to Window ➤ Show View ➤ Connections.
- 2.
Right-click Connections View, and select New Connection.
- 3.
In the Connection Name text box, give a name to the connection.
- 4.
The Host Name field should point to the server where you started the LDAP server. In this case, it’s localhost.
- 5.
The Port field should point to the port of your LDAP server, which is 10389 in this case.
- 6.
Keep Encryption Method set to No Encryption for the time being. Click Next.
- 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.
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.
- 1.
In Apache Directory Studio, get to the LDAP browser by clicking the appropriate LDAP connection in the Connections view.
- 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.
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.
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.
The user you created appears under dc=example,dc=com/ou=users in the LDAP browser.
- 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.
The group you created appears under dc=example,dc=com/ou=groups in the LDAP browser.
Connecting Apache Tomcat to Apache Directory Server (LDAP)
- 1.
Shut down the Tomcat server if it’s running.
- 2.
By default, Tomcat finds users from the conf/tomcat-users.xml file via org.apache.catalina.realm.UserDatabaseRealm.
- 3.Open [TOMCAT_HOME]confserver.xml, and comment out the following line in it:<Resourcename="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.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.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
- 1.
Shut down the Tomcat server if it’s running, and make sure connectivity to the LDAP server works correctly.
- 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.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
- 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.
- 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:<Connectorport="8443"maxThreads="200"scheme="https"secure="true"SSLEnabled="true"keystoreFile="absolute/path/to/catalina-keystore.jks"keystorePass="catalina123"clientAuth="false"sslProtocol="TLS"/>
- 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/recipeYou’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
- 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:<Resourcename="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.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.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.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.
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