基于C#的Xamarin应用与PHP后端REST API安全加固咨询
Great job getting the core auth flow working! Let's lock down the security gaps you've identified—here's a step-by-step breakdown of the fixes and best practices to apply:
1. Fix the Immediate Critical Risks
Your current setup has two huge red flags that need urgent attention:
- Plaintext password transmission via GET: Anyone sniffing network traffic can steal passwords straight from the URL.
- Unsanitized SQL queries: Direct string concatenation makes your database wide open to SQL injection attacks.
Quick Non-Negotiable Fixes
- Switch all auth requests to HTTPS: Without this, all other security measures are useless. Most hosting providers offer free SSL certificates via Let's Encrypt.
- Replace GET login with POST: Mirror your signup flow and send credentials in a JSON body instead of the URL to avoid exposing sensitive data in request logs or network sniffs.
2. Password Hashing (Never Store Plaintext!)
Storing plaintext passwords is a cardinal security sin. Use PHP's built-in password_hash() and password_verify() functions—they're battle-tested, salt passwords automatically, and handle all the complex cryptography for you.
Updated Signup PHP Code
Modify your signup logic to hash passwords before storing them, and add basic client validation:
<?php // Define your Parse-style API keys (generate random, unique values!) define('APP_ID', 'your-custom-app-id-here'); define('REST_API_KEY', 'your-secret-rest-key-here'); // Validate Parse-style request headers first $appId = $_SERVER['HTTP_X_PARSE_APPLICATION_ID'] ?? ''; $restKey = $_SERVER['HTTP_X_PARSE_REST_API_KEY'] ?? ''; if ($appId !== APP_ID || $restKey !== REST_API_KEY) { http_response_code(401); echo json_encode(['error' => 'Invalid client credentials']); exit; } $value = json_decode(file_get_contents('php://input')); $mysql_pekare = new mysqli("serv", "user", "pass", "db"); if ($mysql_pekare->connect_error) { http_response_code(500); echo json_encode(['error' => 'Database connection failed']); exit; } if (!empty($value) && isset($value->Email, $value->Password)) { // Hash the password with PHP's secure default algorithm $hashedPassword = password_hash($value->Password, PASSWORD_DEFAULT); $stmt = $mysql_pekare->prepare("INSERT INTO Login (`Email`, `Password`) VALUES(?,?)"); $stmt->bind_param("ss", $value->Email, $hashedPassword); if ($stmt->execute()) { $userId = $stmt->insert_id; echo json_encode(['UserID' => $userId]); } else { http_response_code(400); echo json_encode(['error' => 'Signup failed']); } $stmt->close(); } else { http_response_code(400); echo json_encode(['error' => 'Missing required fields']); } $mysql_pekare->close(); ?>
3. Secure Login Flow with Session Tokens
Instead of returning any password data (even hashed), generate a secure session token after successful login. Store this token in your database and have the client use it for all subsequent authenticated requests.
Updated Login C# Code
Switch to POST, add Parse-style headers, and store the token securely using Xamarin's SecureStorage:
using Xamarin.Essentials; using Newtonsoft.Json; static public async Task<JObject> LoginUser(string Email, string Password) { var httpClientRequest = new HttpClient(); // Add your Parse-style API headers httpClientRequest.DefaultRequestHeaders.Add("X-Parse-Application-Id", "your-custom-app-id-here"); httpClientRequest.DefaultRequestHeaders.Add("X-Parse-REST-API-Key", "your-secret-rest-key-here"); try { var postData = new Dictionary<string, object> { {"Email", Email}, {"Password", Password} }; var jsonRequest = JsonConvert.SerializeObject(postData); HttpContent content = new StringContent(jsonRequest, System.Text.Encoding.UTF8, "application/json"); var result = await httpClientRequest.PostAsync("https://myURL.com/Signingup/Login.php", content); var resultString = await result.Content.ReadAsStringAsync(); var jsonResult = JObject.Parse(resultString); // Store auth token and user ID securely if login succeeds if (jsonResult.ContainsKey("Token") && jsonResult.ContainsKey("UserID")) { await SecureStorage.SetAsync("AuthToken", jsonResult["Token"].ToString()); await SecureStorage.SetAsync("UserID", jsonResult["UserID"].ToString()); } return jsonResult; } catch { return null; } }
Updated Login PHP Code
Use prepared statements to prevent SQL injection, verify password hashes, and generate a secure token:
<?php define('APP_ID', 'your-custom-app-id-here'); define('REST_API_KEY', 'your-secret-rest-key-here'); // Validate Parse-style headers $appId = $_SERVER['HTTP_X_PARSE_APPLICATION_ID'] ?? ''; $restKey = $_SERVER['HTTP_X_PARSE_REST_API_KEY'] ?? ''; if ($appId !== APP_ID || $restKey !== REST_API_KEY) { http_response_code(401); echo json_encode(['error' => 'Invalid client credentials']); exit; } $value = json_decode(file_get_contents('php://input')); $connectionInfo = new ConnectionInfo(); $connectionInfo->GetConnection(); if (!$connectionInfo->conn) { http_response_code(500); echo json_encode(['error' => 'Database connection failed']); exit; } if (!empty($value) && isset($value->Email, $value->Password)) { // Use prepared statement to eliminate SQL injection risk $stmt = $connectionInfo->conn->prepare("SELECT UserID, Password FROM Login WHERE Email = ?"); $stmt->bind_param("s", $value->Email); $stmt->execute(); $result = $stmt->get_result(); if ($user = $result->fetch_assoc()) { // Verify the password hash matches the provided password if (password_verify($value->Password, $user['Password'])) { // Generate a secure random token (add a `Token` column to your Login table first!) $token = bin2hex(random_bytes(32)); // Store the token in the database for future requests $updateStmt = $connectionInfo->conn->prepare("UPDATE Login SET Token = ? WHERE UserID = ?"); $updateStmt->bind_param("si", $token, $user['UserID']); $updateStmt->execute(); $updateStmt->close(); echo json_encode(['UserID' => $user['UserID'], 'Token' => $token]); } else { http_response_code(401); echo json_encode(['error' => 'Invalid email or password']); } } else { http_response_code(401); echo json_encode(['error' => 'Invalid email or password']); } $stmt->close(); } else { http_response_code(400); echo json_encode(['error' => 'Missing required fields']); } $connectionInfo->conn->close(); class ConnectionInfo { public $conn; public function GetConnection() { $this->conn = mysqli_connect("server", "user","pass", "db"); if (!$this->conn) { die("Connection failed: " . mysqli_connect_error()); } } } ?>
4. About the Parse-Style Request Headers
Adding X-Parse-Application-Id and X-Parse-REST-API-Key is absolutely a valid security layer—it acts as a basic gatekeeper to ensure requests are coming from your legitimate app (blocking random bots or unauthorized clients from hitting your API). Here's how to implement it properly:
- Generate unique, random values for both keys (use a secure random generator, not something guessable)
- Hardcode these values in your Xamarin app (note: decompilation can expose them, so this is an extra layer, not a replacement for user authentication)
- In every PHP endpoint, check that the incoming headers match your predefined keys (like we did in the code examples above)
This adds a simple barrier to entry for attackers, but remember: it doesn't authenticate individual users—you still need session tokens for that.
5. Additional Best Practices
- Add token expiration: Add an
ExpiresAtcolumn to your Login table and invalidate tokens after a set time (e.g., 24 hours). Implement a token refresh flow for long-lived sessions. - Validate all input: Add server-side checks for email format, password strength, and required fields to block malformed requests.
- Avoid exposing sensitive data: Never return database errors or hashed passwords to the client—use generic error messages instead to prevent information leakage.
内容的提问来源于stack exchange,提问作者Carlos Rodrigez

