---
title: Luôn kiểm tra quyền truy cập phía server - không tin vào UI ẩn
impact: HIGH
impactDescription: Ẩn nút/màn hình ở client không ngăn được attacker gọi trực tiếp vào API. Server phải luôn là nơi kiểm tra quyền cuối cùng.
tags: swift, ios, authorization, server-side, api-security, security
---

## Luôn kiểm tra quyền truy cập phía server - không tin vào UI ẩn

iOS app thường ẩn tính năng theo role, nhưng đây chỉ là UX. Server phải enforce authorization: kiểm tra token/role trong mỗi API request và trả về 401/403 khi vi phạm. Client không được là người quyết định quyền cuối cùng.

**Incorrect (chỉ kiểm tra phía client):**

```swift
// !! Chỉ ẩn nút theo role cục bộ - không gọi server check
struct AdminDashboardView: View {
    @EnvironmentObject var authManager: AuthManager

    var body: some View {
        if authManager.currentUser?.role == "admin" {  // Check phía client
            Button("Delete User") { deleteUser() }
        }
    }

    // API bên trong không gửi kèm authorization header
    func deleteUser() {
        let url = URL(string: "https://api.example.com/admin/users/1")!
        var request = URLRequest(url: url)
        request.httpMethod = "DELETE"
        // Không có Authorization header!
        URLSession.shared.dataTask(with: request).resume()
    }
}
```

**Correct (server enforces authorization):**

```swift
struct AdminDashboardView: View {
    @EnvironmentObject var authManager: AuthManager

    var body: some View {
        // UI ẩn chỉ là UX - server vẫn sẽ reject nếu gọi sai
        if authManager.currentUser?.role == "admin" {
            Button("Delete User") { deleteUser() }
        }
    }

    // SAFE: Luôn gửi Bearer token để server kiểm tra quyền
    func deleteUser() {
        guard let token = authManager.accessToken else { return }
        let url = URL(string: "https://api.example.com/admin/users/1")!
        var request = URLRequest(url: url)
        request.httpMethod = "DELETE"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        URLSession.shared.dataTask(with: request) { data, response, error in
            guard let httpResponse = response as? HTTPURLResponse else { return }
            if httpResponse.statusCode == 403 {
                // Server từ chối - xử lý gracefully
                DispatchQueue.main.async {
                    authManager.handleUnauthorized()
                }
            }
        }.resume()
    }
}

// Server-side (ví dụ Vapor): kiểm tra role trong middleware
// app.grouped(RoleMiddleware(requiredRole: "admin"))
//    .delete("admin", "users", ":userId", use: deleteUserHandler)
```

**Tools:** Proxyman (intercept requests), OWASP MASVS-AUTH-1, Burp Suite Mobile
