Goal: seed test data in my PHP application via a Cypress framework in the before:run event using Axios. Running both my application and Cypress framework locally. I only need to seed test data in my app locally.
Note 1: I have already implemented test data seeding via database but I'm challenging myself to implement test data seeding via API as well.
Note 2: my application works in a browser - zero issues.
Problem: I can log in to my app via API call but subsequent API calls don't use the PHPSESSID provided by the login method. Axios is allegedly supposed to automatically save the PHPSESSID from the login call and send it with subsequent calls but it isn't. Therefore, I cannot retain the session/stay logged in to be able to interact with my app in any other respect than to log in.
This is a comparison of the first call and second call. Action, sent headers, received headers, data.
Call to https://localhost/xyz/php/apis/users.php
Logging in...
Sent headers: Object [AxiosHeaders] {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
Received headers: Object [AxiosHeaders] {
date: 'Tue, 04 Feb 2025 20:26:22 GMT',
server: 'Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12',
'x-powered-by': 'PHP/8.2.12',
'set-cookie': [
'PHPSESSID=gp9329kmpfili2r49rl3hqc3no; expires=Tue, 04 Feb 2025 21:26:22 GMT; Max-Age=3600; path=/; secure; HttpOnly; SameSite=None'
],
expires: 'Thu, 19 Nov 1981 08:52:00 GMT',
'cache-control': 'no-store, no-cache, must-revalidate',
pragma: 'no-cache',
'access-control-allow-origin': 'http://localhost/xyz',
'access-control-allow-credentials': 'true',
'access-control-allow-headers': 'Content-Type, Authorization, Cookie',
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
'content-length': '100',
'keep-alive': 'timeout=5, max=100',
connection: 'Keep-Alive',
'content-type': 'text/html; charset=UTF-8'
}
Data:
{
ResponseType: 'redirect',
URL: 'http://localhost/xyz/php/pages/games.php',
'logged in:': true
}
Call to https://localhost/xyz/php/apis/invitations.php
Subsequent API call...
Sent headers: Object [AxiosHeaders] {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
Received headers: Object [AxiosHeaders] {
date: 'Tue, 04 Feb 2025 20:26:22 GMT',
server: 'Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12',
'x-powered-by': 'PHP/8.2.12',
'set-cookie': [
'PHPSESSID=00demprhj3anp6ketbvlkpo36e; expires=Tue, 04 Feb 2025 21:26:22 GMT; Max-Age=3600; path=/; secure; HttpOnly; SameSite=None'
],
expires: 'Thu, 19 Nov 1981 08:52:00 GMT',
'cache-control': 'no-store, no-cache, must-revalidate',
pragma: 'no-cache',
'access-control-allow-origin': 'http://localhost/xyz',
'access-control-allow-credentials': 'true',
'access-control-allow-headers': 'Content-Type, Authorization, Cookie',
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
'content-length': '26',
'keep-alive': 'timeout=5, max=99',
connection: 'Keep-Alive',
'content-type': 'text/html; charset=UTF-8'
}
Data:
{ 'PHP > logged in?': false }
As you can see, the PHPSESSIDs are different, but in theory the second one should be the same as the first one.
The 'Data' outputs are me debugging:
classes/users.php
public function logIn($userName, $password) {
// irrelevant logic here
echo json_encode(
array(
"ResponseType" => "redirect",
"URL" => $redirectURL,
"logged in:" => $_SESSION["loggedIn"]
)
);
}
apis/invitations.php
require($_SERVER["DOCUMENT_ROOT"]."/xyz/php/meta/session_configuration.php");
if ($_SERVER["REQUEST_METHOD"] == "GET" && !isset($_SESSION["loggedIn"])) {
header("Location: ../pages/log_in.php");
exit;
}
require_once("../classes/validators.php");
require_once("../classes/invitations.php");
$validatorsObject = new Validator();
$invitationObject = new Invitation();
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$_POST = get_object_vars(json_decode(file_get_contents("php://input")));
// to process data from non-browser sources
if (array_key_exists("body", $_POST)) {
$_POST = get_object_vars($_POST["body"]);
}
$action = $_POST["action"];
echo json_encode(
array(
"PHP > logged in?" => isset($_SESSION["loggedIn"])
)
);
if (isset($_SESSION["loggedIn"]) && $_SESSION["loggedIn"]) {
switch ($action) {
case "create_invitation":
if (($_POST["type_of_invitation"] == "game" && $validatorsObject->validateUserInput($_POST["players_to_invite"])) || ($_POST["type_of_invitation"] == "automatic_game_agreement" && $validatorsObject->validateUserInput($_POST["players_to_invite"]) && $validatorsObject->validateUserInput(array("games_to_create" => $_POST["games_to_create"])))) {
$inviterId = isset($_POST["inviter_id"]) ? $_POST["inviter_id"] : '';
$invitationObject->createInvitation($_POST["players_to_invite"], $_POST["type_of_invitation"], $_POST["games_to_create"], $inviterId);
}
break;
}
}
}
The second one can't even hit createInvitation() because it's not logged in because it's using the wrong PHPSESSID.
I've tried:
Cypress framework > api-utilities.ts
axios.defaults.withCredentials
to true
.axios.interceptors.request.use
to interrogate what's being sent.import axios from 'axios'
axios.defaults.withCredentials = true
axios.interceptors.request.use(
(config) => {
console.log('Sent headers:', config.headers)
return config
},
(error) => {
return Promise.reject(error)
}
)
export const enum ApiCallMethods {
POST
}
export default class ApiUtilities {
static async callApi(url: string, callMethod: ApiCallMethods, data: object): Promise<any> {
let response
try {
switch (callMethod) {
case ApiCallMethods.POST:
response = await axios.post(url, data, {
headers: {
'Content-Type': 'application/json'
}
})
break
}
console.log('Received headers:', response.headers)
console.log('Data:')
console.log(response.data)
return response.data
} catch (error) {
throw error
}
}
static async logIn() {
await this.callApi('https://localhost/xyz/php/apis/users.php', ApiCallMethods.POST, {
action: 'log_in',
username: 'censored',
password: 'censored'
})
}
}
Added .htaccess
file to my app to implement headers (restarted Apache to ensure changes took effect).
# Allow CORS for all paths in your application
Header set Access-Control-Allow-Origin "http://localhost/xyz"
Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Allow-Headers "Content-Type, Authorization, Cookie"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
# Handle OPTIONS preflight requests
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ - [R=200,L]
</IfModule>
# Redirect HTTP to HTTPS
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
Implemented HTTPS on my app so I could then implement SameSite=None.
meta/session_configuration.php
if (session_status() === PHP_SESSION_NONE) {
session_set_cookie_params([
'lifetime' => 3600, // 1 hour
'path' => '/',
'secure' => true, // Only sent over HTTPS
'httponly' => true, // Prevent access from JavaScript
'samesite' => 'None', // Allow cross-site cookies
]);
session_start();
}
Any ideas?
I am unclear whether I needed to implement the prior 'solutions' but the (last?) solution to this was to manually set the PHPSESSID cookie as follows.
Cypress framework > api-utilities.ts
const configuration = {
headers: {
'Content-Type': 'application/json',
Cookie: ''
}
}
then
to the logIn method in the class to set the cookie in the configuration
variable.await this.callApi('https://localhost/xyz/php/apis/users.php', ApiCallMethods.POST, {
action: 'log_in',
username: 'censored',
password: 'censored'
}).then(response => {
const phpSessionId = response.headers['set-cookie'][0].match(new RegExp(/(PHPSESSID=[^;]+)/))
configuration.headers.Cookie = phpSessionId[1]
})
configuration
variable in the callApi method.axios.post(url, data, configuration)
Success:
Logging in...
Sent headers: Object [AxiosHeaders] {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
Cookie: ''
}
Received headers:
set-cookie: PHPSESSID=e7ik1oqt7f5j48sn0lonoh5slb; expires=Wed, 05 Feb 2025 16:47:27 GMT; Max-Age=3600;
Data:
{
ResponseType: 'redirect',
URL: 'http://localhost/xyz/php/pages/games.php',
'logged in:': true
}
Subsequent API call...
Sent headers: Object [AxiosHeaders] {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
Cookie: 'PHPSESSID=e7ik1oqt7f5j48sn0lonoh5slb'
}
Received headers:
**{no PHPSESSID because its using the one acquired by the logIn method}**
Data:
{"PHP > logged in?":true}
{"ResponseType":"redirect","URL":"http:\/\/localhost\/xyz\/php\/pages\/games.php","Id":"64"}