Hi,
I’m developing a chat plugin and encountering a persistent issue where the session_id
(obtained via Session::getId()
) changes between two sequential AJAX POST requests from the client to my plugin’s API routes. This causes a consent token, tied to the initial session_id
, to become unresolvable.
Problem Overview:
-
Consent: User gives consent via the chat.
- An AJAX POST hits
[code]/api/myvendor/myplugin/consent[/code]
. - In
ChatApiController::handleConsentLog()
, a uniqueX-Consent-Token
is generated and stored in themyvendor_myplugin_consent_tokens
table alongsideSession::getId()
(let’s call thissession_id_A
). The token is sent to the client and stored inlocalStorage
.
- An AJAX POST hits
-
Send Message (Proxy): User sends a chat message.
- An AJAX POST hits
[code]/api/myvendor/myplugin/proxy[/code]
with theX-Consent-Token
andX-CSRF-TOKEN
headers. - In
ChatApiController::handleProxy()
, the token is validated by checking for a matchingtoken
AND the currentSession::getId()
(let’s call thissession_id_B
). -
Issue:
session_id_A
andsession_id_B
are different, so the token stored withsession_id_A
is not found when querying withsession_id_B
.
- An AJAX POST hits
Log Snippet (from handleProxy
illustrating the issue):
[2025-06-02 22:21:05] local.DEBUG: [MyPluginAPI] Proxy: Current Server Session ID (start of request): 'EFLpZ...Bc' [this is session_id_B]
[2025-06-02 22:21:05] local.DEBUG: [MyPluginAPI] Proxy: Searching for token in DB: session_id = 'EFLpZ...Bc', token = '4a68...'
[2025-06-02 22:21:05] local.DEBUG: [MyPluginAPI] Proxy: Token NOT FOUND in DB for combination session_id 'EFLpZ...Bc' and token '4a68...'.
[2025-06-02 22:21:05] local.DEBUG: [MyPluginAPI] Proxy (Debug): Tokens in DB with current session_id 'EFLpZ...Bc': []
[2025-06-02 22:21:05] local.DEBUG: [MyPluginAPI] Proxy (Debug): Sessions in DB with received token '4a68...': [{"id":24,"session_id":"mbXhF...Mo", ...}] [this is session_id_A, with which the token was stored]
(Note: Debug logs confirm the session ID is stable within a single request.)
Key Configuration:
- Effective session settings (from
.env
andconfig/session.php
):-
APP_URL
:https://example.com
-
SESSION_DRIVER
:file
-
SESSION_LIFETIME
:120
-
SESSION_DOMAIN
:.example.com
-
SESSION_SECURE_COOKIE
:true
-
SESSION_COOKIE_PATH
:/
-
SESSION_HTTP_ONLY
:true
-
SESSION_SAME_SITE
:lax
-
SESSION_COOKIE_NAME
:myapp_session
(example name)
-
Relevant Code Snippets:
-
plugins/myvendor/myplugin/routes.php
:use MyVendor\MyPlugin\Classes\ChatApiController; // Anonymized namespace use Illuminate\Support\Facades\Route; Route::group(['prefix' => 'api/myvendor/myplugin'], function () { // Anonymized path Route::post('proxy', fn(Illuminate\Http\Request $request) => (new ChatApiController())->handleProxy($request)); Route::post('consent', fn(Illuminate\Http\Request $request) => (new ChatApiController())->handleConsentLog($request)); });
-
JavaScript
fetch
(inassets/js/script.js
):// Consent call const response = await fetch(CONSENT_LOG_URL, { /* ... headers ... */ credentials: 'include' }); // Proxy call const response = await fetch(PROXY_URL, { /* ... headers ... */ credentials: 'include' });
-
ChatApiController.php
(simplifiedSession::getId()
usage):
InhandleConsentLog()
:$sessionId = Session::getId(); // session_id_A // ... generate token ... Db::table('myvendor_myplugin_consent_tokens')->insert(["session_id" => $sessionId, "token" => $newToken, /* ... */]);
In
handleProxy()
:$sessionId = Session::getId(); // session_id_B $dbTokenDetails = Db::table('myvendor_myplugin_consent_tokens') ->where('session_id', $sessionId) // Fails here ->where('token', $clientToken)->first();
Troubleshooting Done:
-
Session Configuration: Verified settings for
domain
,secure
,http_only
,same_site
(all seem correct). -
JavaScript
fetch
: Addedcredentials: 'include'
to all relevantfetch
calls. -
CORS: The application runs entirely under
https://example.com
, so cross-origin issues seem unlikely. - CSRF Tokens: Handled correctly.
-
Middleware: Plugin API routes should be using the
web
middleware group by default (includesStartSession
).
My Questions:
- Despite the above, what could cause
Session::getId()
to return different values for sequential AJAX POSTs from the same client to the same October CMS instance? - Are there any specific October CMS or Laravel considerations for session handling in plugin API routes that might be overlooked?
- How can I reliably stabilize the
session_id
for these API interactions? - Any further diagnostic steps or potential fixes you can suggest?
Any insights or suggestions would be greatly appreciated as I want to ensure this chat functionality is secure and reliable.