Chapter 4. Secure Coding Practices for Identity Security
Before getting into the strategies and methods of implementing identity security, it helps to understand the best practices for securing identity access within applications. Code security is the first line of defense against a malicious attack and it encompasses more than just writing, compiling, and testing applications. Robust practices should foster defenses at every level of the organization, from every line of code to automation scripts to secure development environment, that puts identity security at the center of human and non-human actors alike.
Like any set of best practices, developers must pick and choose the ones that make the most sense for their organization and project. An e-commerce application may require compliance with payment security standards like PCI DSS, while a healthcare provider may need to comply with the HIPAA guidelines.
Keeping these practices in mind throughout the development process helps you identify and remediate potential issues before they make it into a production application.
The Zero Trust Model
The days of focusing solely on a strong perimeter defense are over. The shift to cloud computing, microservices, open source, AI-generated code, and the IoT has broken our reliance on the single wall of security protection offered by firewalls, API gateways, and similar technologies. Now, we must protect access to internal applications and infrastructure just as much as external interfaces. This challenge of creating a holistic enterprise security program led to the development of Zero Trust models.
Zero Trust, at its core, roots a team’s security culture in skepticism rather than blind trust. It involves the meticulous validation of human and non-human actors, both internal and external, against pre-established security standards. Rather than presume a script, application, or system is invulnerable, Zero Trust recognizes that it’s impossible for developers to protect against the many ways in which a complex system can be exploited.
By advocating that no actor can be trusted until they’re verified, a Zero Trust model strengthens protection against potential breaches at the deepest levels of application code. Similar to a sports team where a single misstep can disrupt an entire play, Zero Trust guides people, processes, and tools to secure every aspect of the enterprise, including identities, endpoints, applications, data, infrastructure, and networks.
Note
For an example of a Zero Trust model, the NIST special publication on Zero Trust Architecture (ZTA) defines seven basic tenets for Zero Trust, replacing the old concept of a monolithic, enterprise-wide security perimeter with a more granular and comprehensive approach.
The Best Practices
Given all possible combinations of technologies, application design, IT environments, external interfaces, and coding decisions, it’s impossible to define a concise list of best practices that cover them all. But we can help you improve code security by identifying the most common techniques that address the most frequent types of attacks that exploit human and non-human interfaces to the outside world.
To make referencing this information easier, I’ve split out the general secure coding best practices from the ones specific to identity security.
Note
The examples presented under each best practice are not exhaustive as it’s not feasible to cover all use cases and application environments in this book.
You can make use of other books and secure coding websites, like OWASP, to dig deeper into specific techniques and technologies not listed here.
General secure coding best practices
Whether Java or Python, source code should be assessed for security vulnerabilities early in the development cycle to catch potential issues before they become reality. You have to know where the code is coming from to best validate its security posture.
It’s good to keep the following six practices in mind when developing and testing your applications.
Input validation
Input validation is the testing of user or application input against expected values to ensure proper data integrity and guard against malicious data entry. This technique ensures that input is within expected ranges, uses the correct data types, and does not contain illegal characters or special characters that may be used to bypass other security controls.
A common misconception is that applications should only perform input validation on the client side, which leads to the myth that any traffic from the client is valid. The reality is that client-side validation alone is not sufficient – think of browser vulnerabilities or man-in-the-middle attacks. You should always consider server-side validation to enhance end-to-end security and this philosophy is key to a Zero Trust model.
Encryption
Encryption transforms data into a scrambled format that can only be deciphered using an appropriate decryption key, helping safeguard data even if it falls into the wrong hands. Two common examples of encryption are:
-
Transport Layer Security (TLS): developers use TLS to ensure data transmitted between a client and a server remains confidential and tamper-proof. By using cryptographic protocols, TLS encrypts data during transmission to reduce the chances of eavesdropping and data manipulation by malicious actors. For instance, HTTPS uses TLS encryption to secure web communications to protect data like user credentials and sensitive transactions.
-
Database field-level encryption: In enterprise applications, sensitive data stored in databases can be protected through field-level encryption. This technique encrypts individual fields or columns within a database to ensure the data remains incomprehensible even if an attacker gains unauthorized access to the system. A common database encryption scheme is AES-256, which uses a 256-bit key length to encrypt and decrypt data blocks.
Resource management
Securing computing resources used by applications, such as databases, files, and memory, contributes to an effective defense, as these mediums offer potential access points for attack.
In terms of securing databases, developers should use techniques like strongly typed parameterized queries, input validation, and output encoding to fend off malicious external entities. If these measures fail, the application should log the attempt and not execute database commands.
For file management, requiring authentication before file upload, validation of file types, and restricting file types to business requirements help mitigate risks.
Applications that directly manipulate memory should enforce input and output control for untrusted data, validate buffer boundaries to protect against overflow, and explicitly close resources rather than rely on garbage collection.
Session management
Securely handling multiple requests to a web-based application or service falls under the domain of session management. In a typical secure interaction between a website and browser, a session starts when the user authenticates their identity using a password – it is the responsibility of the application to ensure controlled access to resources throughout the session’s lifecycle. Similar principles apply to applications and microservices invoking other applications and microservices.
Effective session management guards against unauthorized access, data leakage, and identity breaches. In a banking application, for example, proper session management ensures that a user’s access to their account and transactions is secure by blocking unauthorized access through strict authentication processes and timed session expirations.
Code minification and obfuscation
Code minification and obfuscation are techniques to reduce the readability of source lines of code in the event that malicious actors obtain access to them, to prevent the exploitation of sensitive logic, algorithms, and proprietary information.
Code minification removes unnecessary characters such as white spaces, line breaks, and comments from source code within languages that don’t require them. While minification does not hide the functionality of the code, it does make it much harder to read.
An example of minified JavaScript code looks like this:
function consoleGreet(o){console.log("Hi "+o+", let’s talk about security.")}consoleGreet("John");
Code minification also has the benefit of improving load times and bandwidth usage for website JavaScript, CSS, and HTML.
Code Obfuscation goes a step further to transform code such that it’s nearly impossible for humans to comprehend. Code obfuscation tools rename variables and functions with non-descriptive names, restructure code logic, and introduce redundant code.
Running the above JavaScript code through an obfuscator tool looks something like this:
var _0x18ddd5=_0x59f8;(function(_0xe2e8d1,_0xf81005){var _0x119c75=_0x59f8,_0xdeca8a=_0xe2e8d1();while(!![]){try{var _0x1b8032=-parseInt(_0x119c75(0x1b4))/0x1*(-parseInt(_0x119c75(0x1b6))/0x2)+parseInt(_0x119c75(0x1b1))/0x3*(parseInt(_0x119c75(0x1b0))/0x4)+parseInt(_0x119c75(0x1b2))/0x5*(-parseInt(_0x119c75(0x1ac))/0x6)+-parseInt(_0x119c75(0x1b5))/0x7+parseInt(_0x119c75(0x1ad))/0x8*(-parseInt(_0x119c75(0x1aa))/0x9)+parseInt(_0x119c75(0x1b7))/0xa+parseInt(_0x119c75(0x1ae))/0xb*(parseInt(_0x119c75(0x1af))/0xc);if(_0x1b8032===_0xf81005)break;else _0xdeca8a['push'](_0xdeca8a['shift']());}catch(_0x4c9de6){_0xdeca8a['push'](_0xdeca8a['shift']());}}}(_0x5ad3,0xee366));function sayHi(_0x3d695d){var _0x526d66=_0x59f8;console['log']('Hi\x20'+_0x3d695d+_0x526d66(0x1b3));}function _0x5ad3(){var _0x4c7d22=['237iaKSCx','25QwmRYS',',\x20nice\x20to\x20meet\x20you.','522gRNiwk','9801162nhvSlw','5326oTqclD','1678250xCuqrj','45972PYCeIh','John','215826nYUOoo','2840cPoebI','18548288DPVcbi','12FRMuuy','56960yABSLS'];_0x5ad3=function(){return _0x4c7d22;};return _0x5ad3();}function _0x59f8(_0x1a2dda,_0x1b1ef8){var _0x5ad3bb=_0x5ad3();return _0x59f8=function(_0x59f83b,_0x488c54){_0x59f83b=_0x59f83b-0x1aa;var _0x5e7b9b=_0x5ad3bb[_0x59f83b];return _0x5e7b9b;},_0x59f8(_0x1a2dda,_0x1b1ef8);}sayHi(_0x18ddd5(0x1ab));
Identity security best practices
Identity security is all about making sure each person is who they say they are, giving them the proper permissions, and providing them access to sensitive information in ways that are deliberate and traceable.
The following practices encompass these goals and set the stage for the rest of this book.
Authentication
Authentication is the process of validating a user’s identity to gain access to a system. It answers the question, “Who are you?” by confirming the authenticity of a user’s credentials – human and non-human – to ensure they are who they claim to be.
Implementing authentication involves techniques such as:
- Username and password
-
The most basic and familiar form of authentication, whereby users input a unique username and corresponding password to gain system access. Developers must ensure the secure storage of passwords, avoiding common pitfalls like plain text storage that is easily exploitable by hackers.
- Biometric authentication
-
Uses fingerprints, facial recognition, and other biometric markers to confirm a user’s identity.
- Two-factor authentication (2FA)
-
Combines two different authentication methods, like a password and a one-time code sent to the user’s mobile device, to strengthen identity verification.
Authorization
Authorization is the process of granting or denying access to specific resources or functionalities within an application or system. It answers the question, “What can you do?” by determining the actions a human or non-human user is permitted to perform once an authorization mechanism confirms their identity.
Developers can choose to implement authorization in three ways:
- Role-Based Access Control (RBAC)
-
Assigns roles to users based on their responsibilities, with each role having defined access rights to specific areas of the system.
- Attribute-Based Access Control (ABAC)
-
Evaluates user attributes like job title, location, and clearance level to determine access privileges.
- Policy-Based Access Control (PBAC)
-
Enforces access based on predefined policies that dictate who can access specific resources under which conditions.
Developers should build authorization mechanisms that ensure the most granular level of control (i.e., the principle of least privilege) to ensure human and non-human actors have only the permissions necessary to perform a required task.
Access control
The purpose of access control is to restrict entry to sensitive systems and information. Confining access to only those users who are who they say they are, and possess the necessary authorization to keep sensitive information from falling into the wrong hands.
Proper access control measures should achieve these three goals:
-
Prevent unauthorized entry: access control reduces the risk of unauthorized parties gaining entry to sensitive systems, minimizing the potential for breaches, data leaks, and cyberattacks.
-
Preserve data integrity: by limiting access to authorized personnel only, access control ensures the integrity and accuracy of sensitive data, mitigating the chance of tampering or manipulation by malicious actors.
-
Maintain availability: access control mechanisms are not meant to prevent or delay legitimate users from doing their jobs. Such measures should keep data accessible and available promptly for authorized personnel.
The following Java code sample illustrates a simple access control check using Spring Security, requiring users to have the “ADMIN” role to access the “/admin/” URL pattern:
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and().formLogin(); }
Here, Spring Security’s antMatchers()
method is used to specify a pattern that matches a URL or resource.
Auditing and logging
Developers use various auditing and logging techniques to maintain security accountability and traceability for applications. While auditing focuses on capturing specific system events and user actions in support of compliance activities, logging centers on capturing information about an application’s state, behavior, and interactions with the outside world to support security testing, analysis, and incident response.
The audit process typically follows three stages:
-
Defining audit trails: identifying which events and actions warrant auditing, such as login attempts, data updates, and changes to permissions.
-
Capturing relevant data: live recording of contextual information such as timestamps, user IDs, IP addresses, and actions performed.
-
Storing results: capturing audit logs in a secure and tamper-proof manner to prevent unauthorized changes.
Implementing logging functions usually involves three decisions:
-
Selecting events: choosing which events, errors, and activities warrant logging to satisfy security requirements without bogging down system performance or overloading the security team with too much data.
-
Protecting data: deciding which details are necessary for accurate logging while avoiding the inclusion of sensitive data like passwords and personal information.
-
Managing logs: determining how to centralize log data from various application components to facilitate analysis and monitoring.
Secrets management
Secrets management is the practice of ensuring that sensitive information, like passwords, API keys, certificates, SSH keys, and encryption keys, is stored, accessed, and used in ways that safeguard them from unauthorized access, misuse, and exposure.
The biggest challenge with secrets management is one of scale: secrets are everywhere. Application and IT environments vary significantly between organizations, making it difficult to manage secrets effectively for every application, script, automation tool, and other non-human identity that relies on privileged credentials to access other tools, applications, and data.
Think of hard-coded credentials in containerized applications (e.g., Red Hat OpenShift, Kubernetes, or Azure Kubernetes Service), automation processes (e.g., Ansible Playbooks, Puppet, or Chef), software development tools (e.g., compilers, profilers, code analyzers), cloud infrastructure, and IT management software. Multiply that by the speed of automation – accessing protected data and resources much faster than any human – and you begin to see the scope of secrets management.
Typical secrets management practices include:
-
Authentication of all access requests that use non-human credentials
-
Enforcement of the principle of least privilege
-
Use of RBAC and regular rotation of secrets and credentials
-
Tracking all accesses and performing regular comprehensive audits
-
Removal of all secrets from code, configuration files, and other unprotected areas
Developers use specialized tools and practices to automate secrets management and apply access policies consistently. These tools typically centralize secrets storage, enable secure access controls, and manage credential rotation. The role of the developer is to know what techniques are available and where to deploy them, whether it’s storing credentials in a secrets management system instead of a JSON file or enforcing the principle of least privilege on a containerized build system rather than giving it administrator access.
Secure development environments
Securing development environments may be the first line of defense in identity security, as this is where developers live and breathe every day. It involves establishing and maintaining a controlled setting where software is developed, tested, and maintained, to ensure robust application security at every stage of the development lifecycle.
Here are six techniques to foster a more secure development environment:
-
Avoid hard-coded secrets: Never embed sensitive credentials, like passwords or API keys, directly into application code, scripts, or configuration files. Instead, utilize secure secrets management solutions to safeguard these confidential details.
-
Test on real production systems: While testing software, employ real production systems whenever possible. This practice enables developers to simulate authentic usage scenarios, uncover potential vulnerabilities, and validate system behavior under realistic conditions.
-
Isolate test environments: Prevent test applications and frameworks from accessing live production systems. Isolating these environments minimizes the risk of accidental data leakage or unintentional system disruption during development and testing phases.
-
Enforce segregation of duties: Implement the principle of segregation of duties, ensuring that different individuals can only access specific development components. This reduces the likelihood of unauthorized actions and improves accountability.
-
Maintain open source and supply chain vigilance: Never assume open-source and supply chain software components are secure. Developers should thoroughly assess and monitor libraries, packages, products, and open-source repositories for potential vulnerabilities, adhering to regular updates and patches. This includes the proliferation of AI-generated code and ensuring it meets robust security standards.
-
Test with proper secrets management in place: Always conduct testing with a robust secrets management system in place to ensure sensitive information remains encrypted, properly managed, and inaccessible to unauthorized parties.
These practices not only minimize potential vulnerabilities before they’re introduced into code, but they also fortify the overall tools and IT environment against threats.
Avoiding insecure components
While open-source, third-party, and AI-generated components and libraries make your development life easier, they can also be targets for malicious activity by harboring vulnerabilities and potential exploits. Avoiding components known to be vulnerable and scanning them before, during, and after release helps improve the security posture of your applications.
The Equifax data breach arose from an unpatched vulnerability, CVE-2017-5638, in the open-source Apache Struts framework. This costly incident underscores the need for developers to know the security status of any component before including it in their application, and stay on top of security patches to maintain the latest versions.
Automated scanning and analysis
Automated scanning and analysis tools reduce the manual effort needed to identify vulnerabilities in code and maintain ongoing code security assessments through development changes, testing, and in-market updates. They use different types of techniques and checks to systematically examine code against known vulnerabilities and vulnerability patterns, with many tools reporting deviations from the best practices identified in this chapter.
There are three major categories of automated scanning and analysis tools:
- Static Application Security Testing (SAST)
-
Analyzes source code, bytecode, or binary files to uncover vulnerabilities without executing the program in a runtime environment.
- Dynamic Application Security Testing (DAST)
-
Assesses applications at runtime, often using an emulation or model of the operating environment, to identify vulnerabilities.
- Software Composition Analysis (SCA)
-
Scans for vulnerabilities in open-source components and third-party libraries integrated into an application.
These techniques should be integrated into the development pipeline to help developers pinpoint vulnerabilities earlier in the development lifecycle, typically making it easier and less costly to remediate than if they were found post-release.
Understanding security standards
Industry security standards provide a structured approach to building secure applications, based on tried-and-tested practices used by the individuals and businesses that make up the standards’ communities. These guidelines and examples help developers build security into their development processes. Some industries and customers mandate compliance with security standards before the product can go to market, such as HIPAA compliance in the healthcare industry.
Here are some key industry standards to be aware of and examples of their guidelines.
Common Weakness Enumeration (CWE)
CWE is sponsored by the MITRE Corporation and the community comprises representatives from major operating systems vendors, commercial information security tool vendors, academia, government agencies, and research institutions. CWE and the CWE Top 25 provide a catalog of common software security weaknesses and vulnerabilities, including:
-
Avoiding improper input validation that leads to injection attacks
-
Preventing insecure direct object references that can result in unauthorized data access
-
Incorrect assignment of permissions to critical resources
OWASP
OWASP offers resources and guidance to enhance application security, including the popular OWASP Top 10 used by developers around the world. As a non-profit and open-source initiative to document security threats, OWASP resources are available free of charge to anyone.
Example OWASP guidelines are:
-
Ensuring proper input validation to thwart SQL injection and Cross-Site Scripting (XSS) attacks
-
Employing secure authentication and authorization mechanisms to prevent unauthorized access
-
Logging security and monitoring failures to help detect, escalate, and respond to active breaches
National Institute of Standards and Technology (NIST)
NIST, part of the U.S. Department of Commerce, provides frameworks and guidance on secure software development. Examples include:
-
The NIST Cybersecurity Framework specifies five elements required to protect critical infrastructure, Identify, Protect, Detect, Respond, and Recover, that include activities and target goals to improve cybersecurity outcomes
-
The Secure Software Development Framework (SSDF) describes high-level secure development practices based on established standards and guidance
-
Supporting the President’s Executive Order (EO) on Improving the Nation’s Cybersecurity by identifying standards, tools, best practices, and other guidelines to enhance software supply chain security
Payment Card Industry Data Security Standard (PCI DSS)
PCI DSS specifies over 300 security controls for organizations handling payment card data, following these three principles:
-
Encrypt payment card data during transmission and storage
-
Store data securely, with techniques such as encryption, ongoing monitoring, and testing access to card data
-
Regularly test security systems and processes to identify vulnerabilities
Health Insurance Portability and Accountability Act (HIPAA)
HIPAA sets guidelines for handling patients’ electronic protected health information (ePHI) within the healthcare sector. This law mandates compliance for both covered entities and their third-party Business Associates, with guidelines that include:
-
Implementing strong access controls to limit unauthorized access to sensitive patient data
-
Employing encryption to safeguard ePHI
-
Implementing audit controls to record and track system activities that involve ePHI
Summary
The power to create secure and trustworthy software begins at the developer’s keyboard. From implementing access control and authentication at every level of the application to ensuring proper secrets management across the organization, these best practices form a robust framework to protect against unauthorized access, data breaches, and other forms of cyberattack.
The understanding and use of industry standards like CWE, OWASP, and NIST further enable developers to educate themselves and foster a culture of security-conscious development to meet the expectations of demanding industries and customers.
By incorporating these secure coding best practices, you not only protect your applications and user data but also contribute to a safer digital environment for the world at large.
Get Identity Security for Software Development now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.