Подтвердить что ты не робот

Экспресс-сессия не сохраняется после вызовов CORS

TL; ДР:

Не удается выполнить длительный сеанс при выполнении нескольких вызовов API. между Backbone App и Node.js Server с Express, Express-Session и Express-Cors. Похоже, сеанс повторно инициализирован/потерян после каждого вызова.

Длинная версия:

У меня есть приложение Backbone/React/Flux для клиента, работающее на localhost:3000, выполняющее следующий вызов на сервере Node.js, работающем на localhost:4242:

Http-вызовы

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
 [...]

В основном первый вызов POST /api/session регистрирует пользователя и пытается сохранить токен API в сеансе. Второй вызов GET /api/users запускается сразу после первого успешного завершения и получения информации о пользователе.

Магистральный метод

Здесь мой метод Backbone в модели Session для входа в систему:

  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)

И вызов /api/users в моем UserStore

users: (options) ->
  @users.fetch
    success: (users) =>
      @users = users
      options.success(users)

Используя эти различные параметры (я переопределил Backbone.sync в 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)

(Упрощенная версия: то же самое для обеих моделей и коллекции, используя BaseCollection и BaseModel, где я переопределяю метод sync()).

Так что Console.log(options) в Backbone.sync(method, model, options) возвращается:

{"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 настройка и методы

Здесь настроен мой маршрутизатор Node.js 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'

Здесь мой метод обработки Node.js 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()

И одна обработка GET /api/users

  @express.get '/api/users', (request, response) =>
    console.log JSON.stringify(request.session)
    console.log request.sessionID
    console.log '---------------------------------------------'
    [...]

Node.js Журнал

Здесь журнал:

  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
---------------------------------------------

Как вы можете видеть, что запрос CORS работает нормально, я правильно получаю свой токен, а затем пытаюсь сохранить его в сеансе.

Однако во втором вызове сеанс не сохраняется и я не могу получить доступ к переменной (accessToken), которую я фактически устанавливаю при первом вызове.

Глядя на журнал и на заголовки HTTP двух вызовов, похоже, что сеанс повторно инициализируется каждый раз, когда идентификатор сеанса меняется и каждый запрос - и каждый раз заголовок Set-Request отправляется (что насколько мне известно, не должно быть так.

Я подозреваю, что это поведение вызвано некоторой некогерентной или отсутствующей конфигурацией на уровне CORS или из-за установки path для сеансов (Express.use(path, middleware) и Cookie({path: '/'})). Однако, несмотря на многие попытки с другой конфигурацией, настройкой и заголовками, я действительно не могу заставить его работать.

Любой, кто может просветить меня по этому поведению, и то, что мне не хватает, очень приветствуется:)
Спасибо!

PS: Приношу свои извинения разработчикам non-CoffeeScript;)

4b9b3361

Ответ 1

Здесь ответ (простой): Я неправильно сконфигурировал промежуточное ПО для своей сессии; Проблема cookie.domain вызывала проблему. Здесь правильная конфигурация:

sessionConfig: ->
  secret: 'whatever'
  cookie:
    secure: false
    httpOnly: false

Эта опция не требуется/существует (больше?) с промежуточным программным обеспечением express/session; Не уверен, почему я изначально использовал его, возможно, что-то из устаревшей ссылки (для выражения /cookie -parser или express/cors).