Guidelines

Authentication and Authorization

The subject of Authentication (AuthN) and Authorization (AuthZ) is extremely important due to the frequency of vulnerabilities involving either of them being vulnerable. Since they seem to come up so regularly, that usually means there’s a bit of uncertainty around what they are or even just what causes them. 

As a reminder, each term covers the following:

  • Authentication: Who is the user? 
  • Authorization: What should the user have access to? 

We’ll look at them separately below.

Authentication (identification and authentication failures)

Improper authentication can cover a large variety of vulnerabilities, such as:

  • The absence of authentication on a specific page/endpoint
  • Lack of protection against brute force attacks (Credentials stuffing)
  • Insecure account/password recovery processes
  • Insecure authentication token generation, validation, expiration, transmission, or storage
  • Improper or missing validation that the user has authenticated with 2FA (Where applicable)

The first item on the list (Absence of authentication) is by far the most common issue observed in the wild. Many times, a developer will explicitly have to annotate/configure the level of authentication to be required by a page or endpoint and that step can easily be missed. 

It's a good practice to ensure a system fails closed, rather than fails open. So, rather than annotating each endpoint with the information that they require an authenticated user session, the default should be that all routes require an authenticated user session unless it’s been specifically overridden. Doing this can drastically reduce the room for error.

Authorization (broken access control)

Authorization issues can present themselves in a number of different ways that are very common:

  • Insecure Direct Object References (IDOR)
  • Missing Function Level Access Control (Missing AuthZ)
  • Privilege escalation (Horizontal or vertical)

Insecure direct object references

Objects tend to have unique identifiers (IDs) that are used as a key to reference them. When a user sends a request to view an order, account, or something similar, it usually contains this ID. An "Insecure Direct Object Reference" occurs when the application fails to validate whether the user (or lack thereof) should be able to access that specific object.

Missing function level access control 

Another really common vulnerability is the absence of authorization checks for a page or endpoint (as opposed to an object). 

Depending on the framework used, it's common that developers have to either check for authorization in the handler or annotate the endpoint and specify the requirements needed to call the endpoint. 

Unfortunately, these extra steps are also very easy to forget which often explains how some authorization vulnerabilities end up happening.

Recommendations

Default to closed rather than open

In the case of both Authentication and Authorization, the principle of defaulting to closed instead of open is important. 

Depending on your language/framework, it’s good practice to ensure the default for all routes into your application requires an authenticated session with the highest roles or permissions possible.  Doing this forces a developer to override the requirements for the route. 

cs

// Ensure the default behaviour is to authenticate requests, and check if they are admin
[Authenticate]
[Authorize("Admin")]
public class SecureController : Controller
{

}

public class MyController : SecureController
{

    // Overrides the Authorize attribute inherited to allow any user to access the page
    [Authorize("User")]
    public Page ShowUserProfile() {
        
    }   

    // Can only be accessed by an Admin user
    public Page ShowAdminPage() { 

   }

    // Overrides the Authenticate and Authorize attribute to allow ME
    [AllowAnonymous]
    public Page ShowLoginPage() {
       
    } 

}

Enforce authorization checks in services

When accessing data, it's extremely important to ensure that all data access enforces relevant access and authorization checks in a uniform way. This is generally achieved through the use of domain service.

Further examples

Below, we’ve got a short collection of examples that give a good look at the difference between secure and insecure authentication and authorization. 

C# - insecure

Missing authentication

public class AdminController : Controller
{

    // INSECURE: Does not check whether the user is logged in before showing an Admin page
    public Page ShowAdminPage() {

    }

}

Missing authorization

[Authenticate]
public class AdminController : Controller
{

    // INSECURE: Does not check the Authorization of the user before showing an Admin page public Page ShowAdminPage() {

    }

}

C# - secure

[Authenticate]
[Authorize("Admin")]
public class AdminController : Controller
{

    // SECURE: Both checks that the user is logged in, and has the Admin role
    public Page ShowAdminPage() {

    }

}