(For more resources related to this topic, see here.)
One general piece of advice that applies to every type of application development is to develop the software with security in mind, meaning it is more expensive for an error-prone application to first implement the needed features and after that to make modifications in them to enforce security. Instead, this should be done simultaneously.
In this article we are raising security awareness, and next we will learn about which measures we can apply and what we can do in order to have more secure applications.
TLS (the cryptographic protocol named Transport Layer Security) is the result of the standardization of the SSL protocol (Version 3.0), which was developed by Netscape and was proprietary. Thus, in various documents and specifications, we can find the use of TLS and SSL interchangeably, even though there are actually differences in the protocol.
From a security standpoint, it is recommended that all requests sent from the client during the execution of a grant flow are done over TLS. In fact, it is recommended TLS be used on both sides of the connection.
OAuth 2.0 relies heavily on TLS; this is done in order to maintain confidentiality of the exchanged data over the network by providing encryption and integrity on top of the connection between the client and server. In retrospect, in OAuth 1.0 the use of TLS was not mandatory, and parts of the authorization flow (on both server side and client side) had to deal with cryptography, which resulted in various implementations, some good and some sloppy.
When we make an HTTP request (for example, in order to execute some OAuth 2.0 grant flow), in order to make the connection secure the HTTP client library that is used to execute the request has to be configured to use TLS.
TLS is to be used by the client application when sending requests to both authorization and resource servers, and is to be used by the servers themselves as well. The result is an end-to-end TLS protected connection. If end-to-end protection cannot be established, it is advised to reduce the scope and lifetime of the access tokens that are issued by the authorization server.
The OAuth2.0 specification states that the use of TLS is mandatory when sending requests to the authorization and token endpoints and when sending requests using password authentication. Access tokens, refresh tokens, username and password combinations, and client credentials must be transmitted with the use of TLS.
By using TLS, the attackers that are trying to intercept/eavesdrop the exchanged information during the execution of the grant flow will not be able to do so. If TLS is not used, attackers can eavesdrop on an access token, an authorization code, a username and password combination, or other critical information.
This means that the use of TLS prevents man-in-the-middle attacks and replaying of already fulfilled requests (also called replay attacks). By performing replay attempts, the attackers can issue themselves new access tokens or can perform replays on a request towards resource servers and modify or delete data belonging to the resource owner.
Last but not least, the authorization server can enforce the use of TLS on every endpoint in order to reduce the risk of phishing attacks.
Ensure web server application protection
For client applications that are actually web applications deployed on a server, there are numerous protection measures that can be taken into account so that the server, the database, and the configuration files are kept safe.
The list is not limited and can vary between scenarios and environments; some of the key measures are as follows:
- Install recommended security additions and tools for the given web and database servers that are in use.
- Restrict remote administrator access only to the people that require it (for example, for server maintenance and application monitoring).
- Regulate which server user can have which roles, and regulate permissions for the resources available to them.
- Disable or remove unnecessary services on the server.
- Regulate the database connections so that they are only available to the client application.
- Close unnecessary open ports on the server; leaving them open can give an advantage to the attacker.
- Configure protection against SQL injection.
- Configure database and file encryption for vital information stored (credentials and so on). Avoid storing credentials in plain text format.
- Keep the software components that are in use updated in order to avoid security exploitation.
- Avoid security misconfiguration.
It is important to have in mind what kind of web server it is, which database is used, which modules the client application uses, and on which services the client application depends, so that we can research how to apply the security measures appropriately.
OWASP (Open Web Application Security Project) provides additional documentation on security measures and describes the industry's best practices regarding software security. It is an additional resource recommended for reference and research on this topic, and can be found at https://www.owasp.org.
Ensure mobile and desktop application protection
Mobile and desktop applications can be installed on devices and machines that can be part of internal/enterprise or external environments. They are more vulnerable compared to the applications deployed on regulated server environments. Attackers have a better chance to try to extract the source code from the applications and other data that comes with them.
In order to provide the best possible security, some of the key measures are as follows:
- Use secure storage mechanisms provided by additional programming libraries and by features offered by the operating system for which the application is developed.
- In multiuser operating systems, store user specific data such as credentials or access and refresh tokens in locations that are not available to other users on the same system.
- As mentioned previously, credentials should not be stored in plain text format and should be encrypted.
- If using an embedded database (such as SQLite in most cases), try to enforce security measures against SQL injection and encrypt the vital information (or encrypt the whole embedded database).
- For mobile devices, advise the end user to utilize device lock (usually with a PIN, password, or face unlock).
- Implement an optional PIN or password lock on the application level that the end user can activate if desired (which can also serve as an alternative to the previous locking measure).
- Sanitize and validate the value from any input fields that are used in the applications, in order to avoid code injection, which can lead to changing the behavior or exposing data stored by the client application.
- When the application is ready to be packaged for production use (to be used by end users), perform code analysis for obfuscating code and removing the unused code. This will produce a smaller client application in file size, which will perform the same but it will be harder to reverse engineer.
As usual, for additional reference and research we can refer to the OAuth2.0 threat model RFC document, to OWASP, and to security documentation specific to the programming language, tools, libraries, and operating system that the client application is built for.
Utilize the state parameter
As mentioned, with this parameter the state between the request and the callback is maintained. Even if it is an optional parameter it is highly advisable to use, and the value from the callback response will be validated if it is equal to the one that was sent.
When setting the value for the state parameter in the request
- Don't use predictable values that can be guessed by attackers.
- Don't repeat the same value often between requests.
- Don't use values that can contain and expose some internal business logic of the system and can be used maliciously if discovered.
- Use session values: If the user agent—with which the user has authenticated and approved the authorization request—has its session cookie available, calculate a hash from it and use that one as the state value.
- Or use some string generator: If a session variable is not available as an alternative, we can use some generated programmable value. Some real world implementations do this by generating unique identifiers and using them as state values, commonly achieved by generating a random UUID (universally unique identifier) and converting it to a hexadecimal value.
- Keep track of which state value was set for which request (user session in most cases) and redirect URI, in order to validate that the returned one contains an equal value.
Use refresh tokens when available
For client applications that have obtained an access token and a refresh token along with it, upon access token expiry it is a good practice to request a new one by using the refresh token instead of going through the whole grant flow again.
With this measure we are transmitting less data over the network and are providing less exposure that the attacker can monitor.
Request the needed scope only
As briefly mentioned previously in this article, it is highly advisable to specify only the required scope when requesting an access token instead of specifying the maximum one that is available.
With this measure, if an attacker gets hold of the access token, he can take damaging actions only to the level specified by the scope, and not more. This is done for damage minimization until the token is revoked and invalidated.
In this article we learned what data is to be protected, what features OAuth 2.0 contains regarding information security, and which precautions we should take into consideration.
Resources for Article:
- Deploying a Vert.x application [Article]
- Building tiny Web-applications in Ruby using Sinatra [Article]
- Fine Tune the View layer of your Fusion Web Application [Article]