Yii session ID changes on AJAX request

I have searched this quite extensively on forums but I didn’t find anything relevant, so I guess I’m doing something out of specs here.

I have a .NET app that calls a Yii app, residing on a different server, through AJAX.

The Yii app auto-starts a session and stores the ID in a cookie, if called directly - but if called through AJAX, it doesn’t even set the cookie and renews the session ID every time. I can see the DB log (since it’s a DB session) searching for a different session ID every time.

This is the relevant clip of my Yii config:


[...]

		'session' => array(

			'timeout' => APP_ENV=='prod' ? 1440 : 3600,

			'sessionName' => 'sharedSession',

			'class' => 'CDbHttpSession',

			'autoCreateSessionTable'=> false,

			'connectionID' => 'db',

			'sessionTableName' => 'shared_session',

			'useTransparentSessionID' => !empty($_POST['PHPSESSID']),

			'autoStart' => 'true',

			'cookieMode' => 'only',

		    'cookieParams' => array(

				'domain' => '.mydomain.com',

				'httpOnly' => false,

			), 

			

		),


		'user'=>array(

			'allowAutoLogin'=>true,

			'stateKeyPrefix' => 'usr-',

			'autoRenewCookie' => true, 

		),

[...]

One thing I tried, was accessing the Yii app from the webserver that hosts the .NET app. This time, the AJAX call used the same session ID as the direct call.

So I guess that the different IP from the requester triggered the different session ID. But why does the AJAX calls always change IDs even if they come from the same server (it’s load balanced but I’m accessing the server directly)? And why doesn’t it set the cookie anyway?

If somebody could help that would be very appreciated.

where is you controller code

The session is not started explicitly by some controller code, since it’s set to autoStart. There are session vars being set and read all over the app. Are you looking for some specific controller code?

Is there some kind of authentication done at the server?

Yii will regenerate session id on login.

In fact looking deeper…


public function init()

	{

		parent::init();

		Yii::app()->getSession()->open();

		if($this->getIsGuest() && $this->allowAutoLogin)

			$this->restoreFromCookie();

		else if($this->autoRenewCookie && $this->allowAutoLogin)

			$this->renewCookie();

		if($this->autoUpdateFlash)

			$this->updateFlash();


		$this->updateAuthStatus();

	}

It seems that with allowAutoLogin and autoRenewCookie together, cookie is renewed. Maybe this causes the issue?

EDIT:

This looks a likely suspect… could it be related to the session timing out already?

http://www.yiiframework.com/wiki/321/using-loginrequiredajaxresponse-to-solve-ajax-session-timeout/

Also, this might be worth a scan if you set the cookie differently for ajax:

http://www.yiiframework.com/forum/index.php/topic/4945-yiiapp-request-isajaxrequest/

This also can be an issue with sharing session cookie across different domains.

You have ‘domain’ in session ‘cookieParams’ set to ‘.mydomain.com’ - is this real value from your config or just example? If this is real value - do you actually use “mydomain.com”?

Other settings to check:




        'securityManager' => array(

            //static validation key to support login from cookie on different domains

            'validationKey' => '7e6ada43348fd4da2c6988d039581fa8',

        ),

        'session'=>array(

            'cookieMode' => 'allow',

            'cookieParams' => array(

                'path' => '/',

                'domain' => '.mydomain.com',

            ),

        ),

        'user' => array(

            'identityCookie' => array(

                'path' => '/',

                'domain' => '.mydomain.com',

            ),

            'stateKeyPrefix' => 'bf31acec45bab384b1ddb57d2577ef46'

        ),



In my case I have several yii apps running on different subdomains, so not all settings above may be necessary for you.

Links to check:

http://www.yiiframework.com/forum/index.php/topic/13192-wildcard-subdomain-with-cdbhttpsession-issue/

http://www.yiiframework.com/wiki/135/single-sign-on-across-multiple-subdomains/

@seb: I tried your method, but still the session cookie is set only by the direct request, not by the AJAX call.

Analyzing the response headers I can see this line:


Set-Cookie:sharedSession=0kgmr0l3fibf973fh6q5ehjkr3; path=/; domain=.mydomain.com (<<< No, this is a mock name! Good that you asked though) 



So there is an attempt to set the cookie, but the cookie is not set. Could that be a cross domain issue?

@outrage: good point, altough I don’t know if the autoRenewCookie() function necessarily creates a new session, I guess it just resets the expiration date of the cookie - but for testing purposes I disabled it anyway.

What does "mock name" means? Is this your local testing server domain or not a real name at all?

If this is not a real name then yes, it can be cross domain issue.

To make cookies work you need to setup domain name properly. It should be real domain name browser use - otherwise browser will ignore it.

Also both of your apps - .NET and yii - should be on subdomains of the same domain like yii.localsite.com and net.localsite.com (assuming you have local virtual host "localsite.com"). And this main domain name should be used in configs.

@ seb

I think the language barrier is interfering here and there’s some misunderstanding.

Gattu was saying the domain is fake for the purpose of display in public forum.

Also it’s not necessary to have both applications on the same server. Sending ajax request is no different than request from your own browser. It’s just a client/server ‘handshake’ so to speak.

@ gattu

I’m keen to see you get this fixed because I may come across this myself very soon.

Keep us updated :)

Take a look at these links:

This is very good:

http://devlog.info/2010/03/10/cross-domain-ajax/

@outrage

I do not say that apps should be on the same server - I say that they should by on a subdomain of the same domain (and physically this can be different servers).

As I understand the problem here is not with AJAX, but with cookies - you can not set/get cookies for any domain - browser will not allow access to other domain cookies, so it is important to have equal and correct cookie settings in both apps.

@gattu

How exactly do you do AJAX request from the .NET app? Is the .NET app also a web application and request is sent by the client side (from browser)?

In general - what are you trying to do? Do you want log in the user to both apps when he logs in to the .NET app?

Also looking at headers above: "Set-Cookie:sharedSession=0kgmr0l3fibf973fh6q5ehjkr3; path=/; domain=.mydomain.com" - it does not look like cookie set by Yii app - usual Yii cookie will be like this "Set-Cookie:PHPSESSID=u842q42v548ni1pdo20o9onpv5; path=/; domain=.site.com" (with PHPSESSID).

The AJAX request is sent by a $.ajax() function. Authentication on both PHP and .NET side is done through a single sign-on mechanism that verifies a token against a SSO server.

@seb, sorry about my poor English - the .mydomain.com is a fake domain name (since I don’t want to expose my client’s!). The two apps reside on separate severs but both have a .mydomain.com domain name.

Again, the funny thing happens when I remotely log into the machine that hosts the .NET app, launch the web page on a browser, and the AJAX request successfully sets the cookie.

Not sure about the PHPSESSID part. I’m pretty sure I’m not setting the session cookie manually, but I don’t have another Yii project handy right now to see how the session cookie looks like.

@outrage, I will check out your links and get back to you if I have any ideas. Thanks!

EDIT:

Also, I had to set

header(‘Access-Control-Allow-Origin: *’);

in my Yii app since it was getting blocked.

It is still not clear how it works, correct me if I wrong:

  • you open .NET app in a browser

  • you do something (enter login and password?)

  • server side of the .NET app authenticates you and set session cookie

  • client side of the .NET app sends AJAX request ($.ajax) to the Yii app

  • Yii app should set own seesion cookie, so if the user switches to Yii app it stays logged in

And now if you open .NET app in a browser on the server where .NET app is installed then everything works, but if you do the same on another computer then Yii app fails to set session cookie, is it true? Maybe this depends on browser version (at least "Access-Control-Allow-Origin" may not work in older browsers)?

Also do you have some custom authentication code in Yii app to support your SSO server (custom CUserIdentity, overloaded CWebUser)?

From my experience it is always complex to setup such interaction between different apps and I always do this using debugger.

If you can enable php debugging on the server with Yii app and you can reproduce the problem on your local computer then the best advice here is to run Yii app under debugger. Then you can trigger an ajax request and go step by step into Yii’s authentication code (most of it in the CWebUser class). I think it is easiest way to understand why session cookie re-generated each time and how to fix it.

I have found out something else: the Yii app can’t see any cookie at all when called from AJAX. I set the following debug line:


Yii::trace("Cookies: ". CVarDumper::dumpAsString($_COOKIE));

In a normal request it returns all the expected cookies, in an AJAX request it’s an empty array!

In addition, if I add another debug line:


Yii::trace("Session cookie (Yii): ". CVarDumper::dumpAsString(Yii::app()->request->cookies['sharedSession']));

in a direct request, it gives me this:


Session cookie (Yii): CHttpCookie#1

(

    [name] => 'sharedSession'

    [value] => '<sessionID>'

    [domain] => ''

    [expire] => 0

    [path] => '/'

    [secure] => false

    [httpOnly] => false

    [CComponent:_e] => null

    [CComponent:_m] => null

)

The domain is not set, although my config says so:




		'session' => array(

			'timeout' => APP_ENV=='prod' ? 1440 : 3600,

			'sessionName' => 'sharedSession',

			'class' => 'CDbHttpSession',

			'autoCreateSessionTable'=> false,

			'connectionID' => 'db',

			'sessionTableName' => 'shared_session',

			'useTransparentSessionID' => !empty($_POST['PHPSESSID']),

			//'useTransparentSessionID' => true, 

			'autoStart' => 'true',

			'cookieMode' => 'allow',

			'cookieParams' => array(

				'path' => '/',

				'domain' => '.mydomain.com',

				'httpOnly' => false,

				'name' => 'sharedSession', 

			), 

		), 



My procedure is actually the following:

  • I log into the .NET app through an authentication form

  • .NET verifies my user ID and password against an OAuth-like service, receives an auth token and stores in a cookie

  • I navigate to a page where a request to the Yii app is made through AJAX (jQuery)

(from this point on it’s my guess)

  • The Yii app attempts to find an existing session by reading cookies, finds none

  • Yii automatically opens a session and tries to set a new session cookie, but the cookie is not set

  • The new session ID, though, is set in the DB (it’s a DB session)

  • Yii looks for the auth token in a cookie, but can’t find it. Therefore it throws an authentication error.

  • The new DB session row stays, its data field is empty.

Hi again Gattu,

Please add this near the top of the root .htaccess in the yii app.


# Serve cross-domain Ajax requests, disabled by default.

# enable-cors.org

# code.google.com/p/html5security/wiki/CrossOriginRequestSecurity

<IfModule mod_headers.c>

    Header set Access-Control-Allow-Origin "*"

</IfModule>

Good luck.

But your cookie is set by .NET app, correct?

So if “domain” is not set then it means that cookie is not configured properly on the .NET application side - you need to check it’s code and make sure that it sets cookie with the same properties as in Yii app. This is very likely the source of your problem.

Yii behavior is expected - if there is no session cookie then Yii opens new guest session.

But it is still not clear why it works when you do this from the machine with .NET app - maybe there is some other then IP address difference in this case?

I tried to find what exactly means the cookie with an empty domain and it seems that it mean "localhost" (or maybe the same domain as the webapp uses), maybe you have local Yii app version on the same machine where .NET app is installed and .NET app talks to local version?