Hi all,
Tl;Dr;
I can't succeed persisting session across multiple APIs call performed between a Backbone App and Node.js Server with Express, (express) Session and (express) Cors. It looks like session is reinitialized/lost after every call.
Long version:
I'm having a client Backbone/React/Flux application running on localhost:3000 performing the following call on a Node.js server running on localhost:4242:
Http calls
POST http://localhost:4242/api/session
RESPONSE HEADERS
Content-Type application/json; charset=utf-8
Set-Cookie connect.sid=s%3AFeNYY5GQGvkyRvOym7DhysprePaQr7xP.BrxOPP56k9pDpxQPvwjDFaxkEYoHU%2FAEtNUIXGltqjI; Domain=http://localhost:3000; Path=/
Vary Origin
X-Powered-By Express
access-control-allow-credentials true
access-control-allow-orign http://localhost:3000
[...]
REQUEST HEADERS
Accept application/json, text/javascript, */*; q=0.01
Content-Type application/json; charset=utf-8
Cookie connect.sid=s%3AjP4iADZvRnDHbJcCE8H81Zy6TIehj9BJ.eDOTw44GcHqq8i2dslBGd43tXMQ22ENl31fRizdC8iA
Host localhost:4242
Origin http://localhost:3000
Referer http://localhost:3000/login
[...]
GET http://localhost:4242/api/users
RESPONSE HEADERS
Content-Type application/json; charset=utf-8
Set-Cookie connect.sid=s%3ARxf91_vLMBqzB6xN-0QFIIk_-SyBP9_8.F1Mr%2BVSkYNJ6MqnzO%2BsxxfwXRinIX6th80SoukG1QBM;Domain=http://localhost:3000; Path=/
Vary Origin
X-Powered-By Express
access-control-allow-credentials true
access-control-allow-orign http://localhost:3000
[...]
REQUEST HEADERS
Accept application/json, text/javascript, */*; q=0.01
Content-Type application/json; charset=utf-8
Cookie connect.sid=s%3AjP4iADZvRnDHbJcCE8H81Zy6TIehj9BJ.eDOTw44GcHqq8i2dslBGd43tXMQ22ENl31fRizdC8iA
Host localhost:4242
Origin http://localhost:3000
Referer http://localhost:3000/login
[...]
Basically the first call POST /api/session is logging-in the user and attempting to store an API token in the session. Second call GET /api/users is triggered right after the first one succeed, and retrieving user information.
Backbone method
Here's my Backbone method on the Session model for logging-in :
login: (options) ->
@set {user: options.user, password: options.password}
@save ['user', 'password'],
success: (data) =>
@set({authenticated: true, accessToken: data.accessToken, password: null})
options.success(data) # trigger the second call here
error: (error) =>
options.error(error)
And the call to /api/users in my UserStore
users: (options) ->
@users.fetch
success: (users) =>
@users = users
options.success(users)
Using those different options (I've overridden Backbone.sync in Backbone.Collection/Backbone.Model):
class UsersCollection extends Backbone.Collection
url: '/api/users'
model: UserModel
sync: (method, model, options) ->
options ?= {}
options.url ?= @url
options.dataType ?= 'json'
options.contentType ?= "application/json; charset=utf-8"
options.crossDomain ?= true
options.xhrFields ?= {"withCredentials": true}
super(method, model, options)
(i.e., _Simplified version_: same thing for both Models and Collection, using BaseCollection and BaseModel, where I am overridding the sync() method).
So that Console.log(options) in Backbone.sync(method, model, options) is returning :
{"url":"http://localhost:4242/api/session","dataType":"json","contentType":"application/json; charset=utf-8","crossDomain":true,"validate":true,"parse":true,"xhrFields":{"withCredentials":true}}
Node.js setup and methods
Here's my Node.js router setting-up Express :
BodyParser = require 'body-parser'
Session = require 'express-session'
Cors = require 'cors'
class Router
constructor: (express) ->
@express = express
@express.use BodyParser.json()
@express.use Cors(@corsConfig())
@express.use Session(@sessionConfig())
# Express routes are set here
# @express.post '/api/session', (request, response) => [...]
# @express.get '/api/users', (request, response) => [...]
corsConfig: ->
origin: 'http://localhost:3000'
credentials: true
sessionConfig: ->
secret: 'whatever'
cookie:
secure: false
httpOnly: false
domain: 'http://localhost:3000'
Here's my Node.js method handling POST /api/session
login: (request, response) ->
session = request.session
console.log JSON.stringify(session)
console.log request.sessionID
console.log '---------------------------------------------'
if session.accessToken
console.log 'session with token!'
response.json {accessToken: session.accessToken}
else
console.log 'performing credentialAuthentication'
user = request.body.user
password = request.body.password
@whatever.authentication
user: user
password: password
success: (accessToken) ->
request.session.accessToken = accessToken
console.log JSON.stringify(session)
console.log request.sessionID
console.log '---------------------------------------------!!!'
response.json {accessToken: accessToken}
# also tried with response.send()
And the one handling GET /api/users
@express.get '/api/users', (request, response) =>
console.log JSON.stringify(request.session)
console.log request.sessionID
console.log '---------------------------------------------'
[...]
Node.js Log
Here's the log:
express:router dispatching OPTIONS /api/session
express:router dispatching POST /api/session
{"cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":false,"path":"/"}}
zse18d2zrNRdEXPjFHF0gm3NkONb-_5V
---------------------------------------------
performing credentialAuthentication
{"cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":false,"path":"/"},
"accessToken":"ebab5010f9ece5ea984e4b73f9a46ef3"}
zse18d2zrNRdEXPjFHF0gm3NkONb-_5V
---------------------------------------------!!!
express:router dispatching GET /api/users
{"cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":false,"path":"/"}}
g8YXQEpt_rnWSGdh1nCKMndiI8Lt2UDq
---------------------------------------------
As you can see the CORS request is performing normally, I am properly getting my token and then attempting to store it within the session.
However the session isn't persisted and I can't access the variable (accessToken) I am actually setting in my first call.
Looking at the log and at the HTTP Headers of the two call, it looks like the session is reinitialized each time, as the session ID is changing and each request - and there is a Set-Request header sent every time (which shouldn't be the case, as far as I understand).
I suspect this behavior is caused by some incoherent or missing configuration at the Session (headers) or CORS level, or due to the path set for Session (Express.use(path, middleware), and Cookie({path: '/'}). However, despite many attempt with different configuration, setup and headers, I really can't make it work.
Anyone able to enlighten me on this behavior and what I am missing is very welcome :)
Thank you!
PS: My apologies for the non-CoffeeScript developers ;)
I'm experiencing the same problem!
Hi @henryng24
From : http://stackoverflow.com/questions/33958907/express-session-not-persisting-after-cors-calls
Here's the answer (simple one): I had misconfigured my Session middleware; The cookie.domain options was causing the problem. Here's the proper configuration :
sessionConfig: ->
secret: 'whatever'
cookie:
secure: false
httpOnly: false
This option isn't required/existing (anymore?) with the express/session middleware; Not sure why I originally used it, probably something from an outdated reference (to express/cookie-parser or express/cors).
If this is solving your problem you can probably close the issue.
req.sessionID keeps changing and still doesn't persist even after I set those parameters that you mentioned.
Therefore, when I try to get that particular sessionID, it does not yield anything found in the sessionStore. I'm not sure if it's a timing issue either... it looks it's saving the current one, but when redirected, it creates a new one, which causes different sessionIDs and therefore an infinite redirect loop.
Hi @nakwa , I'm sorry it took me so long to respond here! I'm glad you got it sorted out :)
@henryng24 , as for your issue, I'm going to close this issue, as the original issue has been sorted out, but you are walking to continue conversation here or open a new issue.
Therefore, when I try to get that particular sessionID, it does not yield anything found in the sessionStore. I'm not sure if it's a timing issue either... it looks it's saving the current one, but when redirected, it creates a new one, which causes different sessionIDs and therefore an infinite redirect loop.
What have you been able to debug so far? From what it sounds like, you found that this module is receiving the session ID from the client, goes to look it up in your session store, and it fails, is that right? If so, this may be an issue with your session store code or configuration.
@dougwilson no worries! Is there an example you could link me to? Here's what I have:
var sessionStore = new session.MemoryStore();
app.use(session({
secret: 'secret',
store: sessionStore,
resave: false,
saveUninitialized: false,
proxy: undefined,
cookie: {
secure: true,
maxAge: 36000000
},
rolling: true,
unset: 'destroy'
}));
I've looked at the configurations over and over again... no help. To clarify, a user is first redirected to an identity server and is redirected to the app once they're authenticated. A sessionID is first created when they hit the identity server, and that sessionID is stored in the sessionStore. When they get redirected to the app, the sessionID changes, which causes an infinite redirect loop. Perhaps I'm missing something...?
hi @henryng24 , I don't have any example to provide to you, because it's a weird pattern that is not common and no one has contributed an example to us to show, unfortunately.
A sessionID is first created when they hit the identity server, and that sessionID is stored in the sessionStore. When they get redirected to the app, the sessionID changes
99% of the time, this is because the Cookie header in the request that reached Express was either missing (thus this module thinks there is no session and creates a new one) or somehow it's a stale cookie.
Diagnosing this is probably going to have to lean on you, who can reproduce the issue with your setup (as it's likely a setup/configuration issue), but I can help you with tips! My first tip would be to completely delete all cookies from the web browser you are testing with to see if that solves the issue.
If that does not help, I suggest using a tool external to the web browser like Wireshark or Fiddler to inspect the request your web browser is making to your Apache server. Because you are using SSL, you'll need to load that tool with the private key of your server so it can decrypt the traffic. Verify that there is a Cookie header in the request and it contains the session ID you expect.
If that looks right, then it's possible it's getting lost somewhere in the Node.js application. I suggest connecting a debugger to your Node.js server and setting a breakpoint within Express's source code, probably here: https://github.com/strongloop/express/blob/4.13.3/lib/application.js#L158 At that point, inspect req.headers.cookie to see if it contains the content you expect. If it does, simply start stepping through the code to see what is happening and if it gets lost, overwritten, or if some condition is hit somewhere that prevents the session from being loaded.
thanks @dougwilson!
Most helpful comment
I'm experiencing the same problem!