> ## Documentation Index
> Fetch the complete documentation index at: https://guide.codepure.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Improper Access Control

> Mitigation for improper access control (missing role/permission checks) in Django, Spring Boot, Rails, Express, ASP.NET Core, and Laravel.

## Overview

Improper Access Control occurs when an application fails to properly enforce what a user is allowed to do. This is different from authentication (who you are). This vulnerability means an *authenticated* user (e..g., a "viewer") can perform actions reserved for a different role (e.g., an "admin"), such as accessing an admin panel or deleting data.

## Business Impact

This is a critical vulnerability that can lead to full system compromise. A low-privilege attacker can escalate their privileges, modify or delete any data, and lock out legitimate administrators.

<Card title="Reference Details" icon="book-open" iconType="solid">
  **CWE ID:** [CWE-284](https://cwe.mitre.org/data/definitions/284.html)
  **OWASP Top 10 (2021):** A01:2021 - Broken Access Control
  **Severity:** High
</Card>

## Framework-Specific Analysis and Remediation

All frameworks provide powerful, declarative, and imperative ways to handle authorization. The vulnerability is almost always a developer *forgetting* to apply these controls to a new endpoint or function. The principle is "Deny by Default."

<Tabs>
  <Tab title="Python">
    #### Framework Context

    Django and DRF provide view decorators (`@login_required`, `@permission_required`) and `permission_classes` on views. The vulnerability is a view that lacks these.

    #### Vulnerable Scenario 1: A Missing Permission Check

    An admin view is created that only checks if the user is logged in, not if they are an admin.

    ```python theme={null}
    # reporting/views.py
    from django.contrib.auth.decorators import login_required

    @login_required
    def generate_all_sales_report(request):
        # DANGEROUS: Any logged-in user can access this, not just staff.
        # It should check for `is_staff` or a specific permission.
        report = Sales.objects.generate_full_report()
        return HttpResponse(report, content_type="text/csv")
    ```

    #### Vulnerable Scenario 2: DRF ViewSet

    A DRF `ModelViewSet` that has no `permission_classes` set.

    ```python theme={null}
    # api/views.py
    class UserViewSet(viewsets.ModelViewSet):
        queryset = User.objects.all()
        serializer_class = UserSerializer
        # DANGEROUS: This ViewSet allows ANY authenticated user
        # (or even anonymous if default is AllowAny) to LIST, CREATE,
        # UPDATE, and DELETE users.
        # permission_classes = [permissions.IsAuthenticated] # Still too permissive!
    ```

    #### Mitigation and Best Practices

    For function-based views, use `@user_passes_test(lambda u: u.is_staff)`. For class-based views, use `UserPassesTestMixin`. For DRF, set `permission_classes` explicitly, e.g., `permission_classes = [permissions.IsAdminUser]`.

    #### Secure Code Example

    ```python theme={null}
    # reporting/views.py (Secure Version)
    from django.contrib.auth.decorators import user_passes_test

    @login_required
    @user_passes_test(lambda u: u.is_staff)
    def generate_all_sales_report(request):
        # SECURE: Now only staff members can access this view.
        report = Sales.objects.generate_full_report()
        return HttpResponse(report, content_type="text/csv")
        
    # api/views.py (Secure Version)
    from rest_framework import permissions

    class UserViewSet(viewsets.ModelViewSet):
        queryset = User.objects.all()
        serializer_class = UserSerializer
        # SECURE: Only Admin users can access this ViewSet.
        permission_classes = [permissions.IsAdminUser]
    ```

    #### Testing Strategy

    Write integration tests where you authenticate as a non-admin user. Attempt to access the admin endpoint and assert that the response is a `403 Forbidden`.

    ```python theme={null}
    # reporting/tests.py
    def test_sales_report_is_forbidden_for_normal_user(self):
        # self.client is logged in as a non-staff user
        response = self.client.get(reverse('sales-report'))
        self.assertEqual(response.status_code, 403)
    ```
  </Tab>

  <Tab title="Java">
    #### Framework Context

    Spring Security provides method-level security (`@PreAuthorize`, `@Secured`) and configuration-level security (`http.authorizeRequests()`). The vulnerability is a public endpoint that should be protected.

    #### Vulnerable Scenario 1: Unprotected Controller Method

    An admin-only method in a controller has no security annotation.

    ```java theme={null}
    // controller/AdminController.java
    @RestController
    @RequestMapping("/admin")
    public class AdminController {
        
        @GetMapping("/dashboard")
        public String getAdminDashboard() {
            // DANGEROUS: If the global config doesn't secure "/admin/**",
            // this endpoint is open to any user, or even anonymous.
            return "Admin Dashboard Data";
        }
    }
    ```

    #### Vulnerable Scenario 2: Weak Global Configuration

    The `WebSecurityConfigurerAdapter` secures some paths but forgets the new admin path.

    ```java theme={null}
    // config/SecurityConfig.java
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/users/**").hasRole("USER")
            // DANGEROUS: The "/admin/**" path is forgotten and left open.
            .antMatchers("/", "/home").permitAll()
            .anyRequest().authenticated();
    }
    ```

    #### Mitigation and Best Practices

    Apply method-level security as a "deny-by-default" policy. It's safer to annotate each method than rely on global config. Enable method security with `@EnableGlobalMethodSecurity(prePostEnabled = true)`.

    #### Secure Code Example

    Use `@PreAuthorize` on the method or controller.

    ```java theme={null}
    // config/SecurityConfig.java (Secure)
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true) // Enable method security
    public class SecurityConfig extends WebSecurityConfigurerAdapter { ... }

    // controller/AdminController.java (Secure Version)
    @RestController
    @RequestMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')") // SECURE: Applies to all methods in this controller
    public class AdminController {
        
        @GetMapping("/dashboard")
        public String getAdminDashboard() {
            return "Admin Dashboard Data";
        }
    }
    ```

    #### Testing Strategy

    Write a MockMVC test using `@WithMockUser` to simulate a request from a user *without* the 'ADMIN' role. Assert that the response is a `403 Forbidden`.

    ```java theme={null}
    @Test
    @WithMockUser(roles = "USER") // Simulate as a regular user
    void getAdminDashboard_asUser_shouldBeForbidden() throws Exception {
        mockMvc.perform(get("/admin/dashboard"))
            .andExpect(status().isForbidden());
    }
    ```
  </Tab>

  <Tab title=".NET(C#)">
    #### Framework Context

    ASP.NET Core Identity uses `[Authorize]` attributes. These can be applied to controllers or individual action methods. A missing attribute is a critical flaw.

    #### Vulnerable Scenario 1: Missing Authorize Attribute

    An `AdminController` is created, but the developer forgets to add the `[Authorize]` attribute.

    ```csharp theme={null}
    // Controllers/AdminController.cs
    // DANGEROUS: This entire controller is accessible by anonymous users.
    public class AdminController : Controller
    {
        public IActionResult Index() { ... }
        
        [HttpPost]
        public IActionResult DeleteUser(string id) { ... }
    }
    ```

    #### Vulnerable Scenario 2: Weak Role Check

    The controller checks for authentication, but not for the specific "Admin" role.

    ```csharp theme={null}
    // Controllers/AdminController.cs
    [Authorize] // DANGEROUS: Any logged-in user can access this.
    public class AdminController : Controller
    {
        public IActionResult Index() { ... }
    }
    ```

    #### Mitigation and Best Practices

    Apply `[Authorize(Roles = "Admin")]` to the controller. You can also define "Policies" in `Startup.cs` (e.g., `policy.RequireRole("Admin")`) and use `[Authorize(Policy = "AdminOnly")]`.

    #### Secure Code Example

    ```csharp theme={null}
    // Controllers/AdminController.cs (Secure Version)
    [Authorize(Roles = "Admin")] // SECURE: Only users in the "Admin" role can access.
    public class AdminController : Controller
    {
        public IActionResult Index() { ... }
        
        [HttpPost]
        public IActionResult DeleteUser(string id) { ... }
    }
    ```

    #### Testing Strategy

    Write an integration test that authenticates a client as a normal user. The client then makes a request to the `AdminController` action and asserts a `403 Forbidden` or a redirect to the login page.

    ```csharp theme={null}
    [Fact]
    public async Task AdminController_Index_ForbiddenForNormalUser()
    {
        // _client is authenticated as a non-admin user
        var response = await _client.GetAsync("/Admin/Index");
        
        // Assert it's forbidden (or a redirect if not authenticated)
        Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
    }
    ```
  </Tab>

  <Tab title="PHP">
    #### Framework Context

    Laravel uses "middleware" to protect routes. The vulnerability is defining a route in `routes/web.php` or `routes/api.php` that is not part of a middleware group.

    #### Vulnerable Scenario 1: Route Outside Middleware Group

    A developer adds a new admin route but forgets to put it inside the admin middleware group.

    ```php theme={null}
    // routes/web.php

    Route::middleware(['auth', 'admin'])->group(function () {
        Route::get('/admin/dashboard', [AdminController::class, 'dashboard']);
    });

    // DANGEROUS: This route is outside the group and accessible to anyone.
    Route::get('/admin/delete-user/{id}', [AdminController::class, 'deleteUser']);
    ```

    #### Vulnerable Scenario 2: Weak Gate/Policy

    A `Gate` is defined for an action but the logic is flawed.

    ```php theme={null}
    // app/Providers/AuthServiceProvider.php
    Gate::define('view-admin-panel', function (User $user) {
        // DANGEROUS: Logic is missing, so it implicitly returns null (denies).
        // Or worse: `return $user->is_active;` (any active user is admin)
    });
    ```

    #### Mitigation and Best Practices

    Ensure all admin-related routes are inside a `Route::middleware([...])->group(...)` block. Use policies (`php artisan make:policy`) to organize authorization logic for models.

    #### Secure Code Example

    ```php theme={null}
    // routes/web.php (Secure Version)

    // SECURE: All routes in this group require auth and admin checks.
    Route::middleware(['auth', 'admin'])->group(function () {
        Route::get('/admin/dashboard', [AdminController::class, 'dashboard']);
        Route.get('/admin/delete-user/{id}', [AdminController::class, 'deleteUser']);
    });

    // app/Providers/AuthServiceProvider.php (Secure Version)
    Gate::define('view-admin-panel', function (User $user) {
        // SECURE: Explicitly check the user's role.
        return $user->role === 'admin';
    });
    ```

    #### Testing Strategy

    Write a feature test. First, `actingAs` a non-admin user. Then, make a request to the admin-only route. Assert the response status is `403 Forbidden`.

    ```php theme={null}
    // tests/Feature/AdminAccessTest.php
    public function test_normal_user_cannot_access_admin_panel()
    {
        $user = User::factory()->create(['role' => 'user']);
        
        $response = $this->actingAs($user)
                         ->get('/admin/dashboard');

        $response->assertStatus(403);
    }
    ```
  </Tab>

  <Tab title="Node.js">
    #### Framework Context

    Express relies entirely on middleware. Authorization is a custom middleware function that developers must write and apply. Forgetting to apply it is the vulnerability.

    #### Vulnerable Scenario 1: Missing Route-Level Middleware

    An admin route is defined without the corresponding `ensureAdmin` middleware.

    ```javascript theme={null}
    // app.js
    const ensureAuthenticated = (req, res, next) => { ... };
    const ensureAdmin = (req, res, next) => {
        if (req.user && req.user.role === 'admin') return next();
        res.status(403).send('Forbidden');
    };

    app.get('/dashboard', ensureAuthenticated, (req, res) => { ... });

    // DANGEROUS: This route is missing the 'ensureAdmin' middleware.
    app.get('/admin/reports', ensureAuthenticated, (req, res) => {
        // Any logged-in user can access this.
        res.send('Admin Reports');
    });
    ```

    #### Vulnerable Scenario 2: Flawed Middleware Logic

    The middleware logic is incorrect.

    ```javascript theme={null}
    // app.js
    const ensureAdmin = (req, res, next) => {
        // DANGEROUS: 'admin' (string) vs. true (boolean)
        // This check might always fail or pass incorrectly.
        if (req.user.isAdmin === 'true') return next(); 
        res.status(403).send('Forbidden');
    };
    ```

    #### Mitigation and Best Practices

    Use `app.use('/admin', ensureAdmin)` to apply the middleware to *all* routes starting with `/admin`. This is a "deny-by-default" approach that's safer than applying it to each route individually.

    #### Secure Code Example

    ```javascript theme={null}
    // app.js (Secure Version)
    const ensureAdmin = (req, res, next) => {
        if (req.user && req.user.role === 'admin') {
            return next();
        }
        res.status(403).send('Forbidden');
    };

    // SECURE: Apply the 'ensureAdmin' middleware to all routes
    // under '/admin'.
    app.use('/admin', ensureAuthenticated, ensureAdmin);

    // This route is now protected by the middleware above.
    app.get('/admin/reports', (req, res) => {
        res.send('Admin Reports');
    });

    app.get('/admin/users', (req, res) => {
        res.send('Admin User List');
    });
    ```

    #### Testing Strategy

    Use Jest/Supertest. Log in as a non-admin user (e.g., using a mock agent) and attempt to GET the `/admin/reports` endpoint. Assert the response status is `403`.

    ```javascript theme={null}
    // tests/admin.test.js
    it('should forbid access to /admin/reports for non-admin users', async () => {
        // 'userAgent' is a supertest agent logged in as a 'user' role
        const response = await userAgent.get('/admin/reports');
        
        expect(response.statusCode).toBe(403);
    });
    ```
  </Tab>

  <Tab title="Ruby">
    #### Framework Context

    Ruby on Rails uses `before_action` filters in controllers. The vulnerability is a controller action that is not covered by a `before_action` that checks for admin privileges.

    #### Vulnerable Scenario 1: Action Not in `only` or `except`

    A `before_action` is applied, but the new, dangerous action is forgotten.

    ```ruby theme={null}
    # app/controllers/admin_controller.rb
    class AdminController < ApplicationController
      before_action :require_admin, only: [:index]

      def index
        # This is protected
      end

      def delete_all_users
        # DANGEROUS: This action is not in the `only` list,
        # so it has no `before_action` filter.
        User.all.destroy_all
      end

      private
      def require_admin
        redirect_to root_path unless current_user&.admin?
      end
    end
    ```

    #### Vulnerable Scenario 2: Flawed Pundit/CanCanCan Policy

    Using a gem like Pundit, but the policy logic is wrong.

    ```ruby theme={null}
    # app/policies/post_policy.rb
    class PostPolicy < ApplicationPolicy
      def destroy?
        # DANGEROUS: This allows a user to destroy a post if they
        # are the author OR an admin. But it should also check
        # that the record hasn't been published, for example.
        # This is "improper", not just "missing".
        user.admin? || record.user == user
      end
    end
    ```

    #### Mitigation and Best Practices

    Apply `before_action` filters to the entire controller without `only` or `except`. This is "deny-by-default." If some actions must be public, use `skip_before_action` on those specific, safe actions.

    #### Secure Code Example

    ```ruby theme={null}
    # app/controllers/admin_controller.rb (Secure Version)
    class AdminController < ApplicationController
      # SECURE: This filter applies to EVERY action in this controller.
      before_action :require_admin

      def index
        # This is protected
      end

      def delete_all_users
        # This is now also protected
        User.all.destroy_all
      end

      private
      def require_admin
        redirect_to root_path, alert: "Access Denied" unless current_user&.admin?
      end
    end
    ```

    #### Testing Strategy

    Write an RSpec request or controller spec. Log in as a non-admin user. `post` to the `delete_all_users` action and assert that the user count has not changed and that the response was a redirect.

    ```ruby theme={null}
    # spec/requests/admin_spec.rb
    it "prevents non-admins from deleting users" do
      create_list(:user, 5)
      login_as(create(:user, admin: false)) # Log in as non-admin
      
      expect {
        post delete_all_users_path
      }.to_not change(User, :count)
      
      expect(response).to redirect_to(root_path)
    end
    ```
  </Tab>
</Tabs>
