Table of contents
You are a developer working on your app, and while you know security is critical to building trust, you'd rather focus on the cooler features of your application.
Why this post on Application Security?
As I started my journey into modern application security, I found the details and jargon confusing. It was not clear where one should start. What should I be worried about? How do I prioritize these concerns? Could there be another big piece to consider?
I was missing a mind map (aka big picture or schema) of the security concerns the developer needed to think about.
As I got into the details, the mind map helped me relate how a seemingly typical dev story on the agile board contributed to the bigger security objective. I needed a starting point to help me get to a big picture.
The goal of this post is to introduce the developer to the concepts required to securely build and deploy an application, and to start thinking about how to handle such security concerns.
I'll discuss the high-level ideas with links to other posts that provide more programmatic examples.
But first, what is Application Security?
Application Security is concerned with development artifacts and practices to ensure that the application's user is performing only authorized actions.
Expounding just a bit, development artifacts would relate to data and code. Practices would affect what a developer does from training, design, coding, building, and deployment. And the term 'user' here would refer to a person or system that is interacting with an application. Lastly, I use the term application to mean a web app, mobile app, or even an API.
So what are the foremost Application Security concerns?
- Vulnerabilities - Is the app protected from unauthorized outcomes?
- Authentication - Is the User who they claim to be?
- Authorization - Is the User entitled to do what they are planning on doing?
Vulnerabilities - Is the app protected from unauthorized outcomes?
Vulnerabilities are flaws in the application that can be exploited allowing the actor to perform an unauthorized action.
Let's start with vulnerabilities. Developers need an understanding of the vulnerabilities and common ways to mitigate them. The OWASP Top 10 is a great starting point, with recent updates for 2022. The list has been reprioritized to reflect the risk level in the wild. Take for example Injection, wherein additional user input is treated as code which could cause a bad actor to access unauthorized data. This used to be #1 in the 2017 list but has slid to #3.
More telling is that the OWASP's top vulnerability is now Broken Access Control, up from #5 in 2017. This covers authentication and authorization -- the other core concepts in this post.
Here are common ways to mitigate these vulnerabilities:
- Enhancing developer education on the risks and mitigations.
- Conduct code reviews that include secure coding practices.
- Use static code analysis tools (aka Static Application Security Testing SAST) while coding (via IDE plugins) or as part of the build process, to detect libraries with vulnerabilities, and insecure coding patterns.
- Use security libraries that mitigate against runtime vulnerabilities such as those that handle encoding and input validation.
- Use code scanning tools that check for vulnerabilities at runtime, poking the application for potential vulnerabilities, and sometimes using some internally deployed agent to analyze the internal call patterns as well. There are several categories of tools which are usually referred to as DAST-Dynamic Application Security Testing, IAST-Interactive Application Security Testing, RASP-Runtime Application Self Protection.
- Use only libraries whose Common Vulnerability Scoring System (CVSS aka CVE Risk Score) score is acceptable to the use case. CVSS scores of 7 and greater usually require immediate mitigation. A recent (December 2021) famous issue is the Log4J CVE in 2021 which had a score of 10. The suspect libraries would usually be flagged by the SAST utility.
Authentication: How do you Authenticate Users logging into your App?
Next on the list -- Authentication. Authentication ensures that the user is who they claim to be.
We experience this at the airport security line when the agent reviews our driver's license to check that we are the person we say we are, and the boarding pass to ensure that we are entitled to that specific seat on that flight.
There are conceptual similarities here to how we secure apps today -- the driver's license would be the ID Token, and the boarding pass would be the Access Token. We'll discuss those tokens shortly, but hopefully, this analogy helps demystify modern application security. Here's a good post that provides a deeper dive between the two tokens.
|Driver's License ~ ID Token||Boarding Pass ~ Access Token|
A very common authentication scenario is a user logging into a web app to view the account balance. The user types in the URL of your application into the browser, and the browser sends the request to your application.
The request makes its way to the Authorization Server (aka AuthServer such as Okta, or Google) which checks if the request has been authenticated. If not, it displays the login form to the user. After the user provides the proper credentials, the AuthServer would return the ID Token as proof of authentication, and the Access Token used for the authorization phase of the request. See step 3 in the sample flow. This flow is also called the Auth Code Flow by the OAuth2 spec and enhanced by the OpenID Connect spec (aka OIDC).
However, practitioners realized that the Auth Code Flow needed to be updated to better secure a mobile app or SPA. The concern was around keeping credentials at the client side to a minimum. This updated option is the Auth Code Flow with Proof of Key Exchange.
A more recent trend within a corporate setting is for authentication to be delegated to providers like Okta, Azure handling user information, credentials, and evolving authentication mechanisms like multi-factor authentication. These are usually cloud-based providers connected to other components in the enterprise to complete the login flow. Additionally, the developer would need to navigate the various standards, patterns, processes, and accelerators that encompass the enterprise security infrastructure.
For a developer that is just releasing their first app, the overall approach is leaning more into the use of social logins from Google, Facebook. The needs are still simpler, and so is the architecture and development process. Using social logins is also better from a user experience perspective, as it avoids the hassle of having to 'sign in again', or 'create another account'.
With either approach, the outcome is the proof of authentication, in a JSON Web Token format (aka JWT spoken as jot) that the AuthServer provides with some user identifier like the email address. A major benefit OIDC provides is formalizing the ID Token as a JWT with a standard list of claims (what one would call fields in the good old days). Given this standard schema, predictable data exchange can be had between components in the request flow. Coding to read the values in the JWT, and to handle some other AuthServer integrations, may require the use of vendor-supplied client libraries.
The user identifier is then harnessed as the logical key in your application's database to associate attributes about this user, and conduct application-specific processing.
Authorization: Are you allowed to do that?
Finally -- authorization. Now that we know who you are, are you permitted to do what you want to do?
Authorization is the process of verifying that the user is entitled to act on the resource.
|User||The entity performing the action. Usually a person or system.|
|Action||The activity that the user wants to perform.|
|Resource||The thing that is being acted upon.|
|Beneficiary||An entity that acquires the value from the action.|
Usually the user and beneficiary are the same entity. However, there are enough cases wherein these two roles are in separate entities that it is good to include these in high-level scope discussions. A common scenario is delegating access to a third party such as an investment advisor (user) reviewing the client's portfolio (beneficiary). This case is captured in the Impersonation and Delegation scenarios in the OAuth2 Token Exchange spec.
So how will requests be authorized for your application?
As a developer, before your application displays that webpage or your API returns that account balance, you need to check that the request has been approved and authorized. This approval is determined either in a centralized component (e.g. AuthServer, OpenPolicyAgent OPA), locally within application code, or a combination thereof, depending on the architecture and context of your solution.
The approval is usually determined by combining user data and use case specific data. For example, the AuthServer may have provided an Access Token that proves the user can access a specific API but there may be logic inside the API to ensure that the user can see the balance of a specific account.
Once the approval has been derived, the application would enforce the authorization check right at the method that provides the business value. The closer to the final bit of code, the more secure the placement is. Here's an example using Spring and Okta. By extension, every network hop of a request that could potentially provide some business value should have an authorization check. In a microservices setup, keep in mind that there might be more hops that would need these checks.
What if it's a System that is requesting into another System?
Another common scenario is when your application needs to call into another application, such as batch jobs calling APIs or even API1 calling API2 based on a user request. Since this is a machine-to-machine interaction that does not involve a person, OAuth2 specifies a different flow called the Client Credentials Flow.
Your application behaving as a consumer would need to acquire credentials (e.g. client id and secret) from the AuthServer that would approve the runtime request. At runtime, the AuthServer recognizes the credentials and provides an access token. For this Client Credential Flow, the AuthServer usually only returns the Access Token, and not the ID Token. More details are here.
Once the producing application has validated the Access Token, it trusts that the request is authorized and returns the requested outcome.
Here's a grid of the concerns and approaches we've discussed.
|Vulnerabilities||OWASP Vulnerabilities and Mitigation|
|Authentication - Web App||OIDC Auth Code Flow|
|Authentication - Mobile, SPA||OIDC Auth Code Flow with PKCE|
|Authorization||Application-specific method level enforcement|
|System to System Calls||OAUTH2 Client Credential Flow|
Delivering secure applications is a core responsibility of the developer. As a developer, vulnerabilities, authentication, and authorization are day one concerns when delivering applications users can trust.
Keep the following questions in mind:
- How are you going to mitigate against vulnerabilities?
- What are your authentication scenarios and how are you going to handle them?
- How are you going to handle authorization?
- Identification icons created by Designer_Nata - Flaticon
- Authorization icons created by Wichai.wi - Flaticon
- Programmer icons created by monkik - Flaticon
- Driver's License Photo, United States Department of Homeland Security, Public domain, via Wikimedia Commons
- Boarding Pass Photo, Piergiuliano Chesi, Public domain, via Wikimedia Commons