Skip to content

Handling errors with “Session token is expired.” for automatic (anonymous) users #4799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
AlexGingell opened this issue May 31, 2018 · 20 comments

Comments

@AlexGingell
Copy link

AlexGingell commented May 31, 2018

Issue Description

My iOS app uses Parse Server's automatic user feature, namely users do not have to provide login credentials, but instead are logged in automatically and anonymously. These are not users that have been migrated from Parse.com, but automatic users with revocable sessions generated by hosted Parse Server.

Our client-side Parse initialisation looks like this:

[Parse initializeWithConfiguration:[ParseClientConfiguration configurationWithBlock:^(id<ParseMutableClientConfiguration> _Nonnull configuration) { 
    configuration.applicationId = <our app id>; 
    configuration.clientKey = <our client key>; 
    configuration.server = <our server>; }]];

// Users 
[PFUser enableRevocableSessionInBackground]; 
[PFUser enableAutomaticUser];
[PFACL setDefaultACL:[PFACL ACL] withAccessForCurrentUser:YES];

On the server, "Expire inactive session" is YES, and "Revoke session on password reset" is YES. Session length is 1 year in seconds. I do not fully understand what constitutes an "inactive" session as far as "Expire inactive session" goes, and exactly what "Expire" constitutes - deletion from the database, or just making the token invalid. It seems to me that the session remains valid simply for "session length" after its inception, regardless of other factors.

Based on that understanding, everything works as expected - the user can communicate with the database - and at the 1 year mark, the session token appears to expire naturally.

With a standard login flow, one would clearly then display a screen allowing the user to login and refresh their token. However, in the case of automatic or anonymous users, that makes no sense - they never entered any login credentials, and so they have no means of refreshing their credentials manually.

Given that automatic user is enabled, I would have expected that Parse Server would automatically generate a new session token for a user with anonymous credentials. Instead, any interaction with the server or database fails with:

Error Domain=Parse Code=209 "Session token is expired." UserInfo={code=209, temporary=0, error=Session token is expired., NSLocalizedDescription=Session token is expired.}

I note that in Parse Server's authentication tab "App authentication settings", that "Enable Anonymous Authentication" is actually set to NO. My understanding here is that this would simply let users communicate with Parse server without a valid session token or valid PFUser object. This is not really what we want - we want a valid anonymous PFUser to communicate, as we may later want to transition then to a regular PFUser. I've tried switching it to YES, but we still see the same errors.

I am using Parse Server v2.3.2 with MongoDB v3.0.12, and iOS Parse SDK v1.17.1. I am looking to upgrade to the latest versions, but I want to take the time to understand what the problem is first, and if/how this may solve the problem.

Is this a known bug, solved by a later version or not? Is this a problem with my implementation or understanding? Can anyone help me with a solution?

Deleting the app and reinstalling it fixes the problem, because a new automatic user is generated and a fresh session token is provided. In the database this appears as a completely new user and the old one sits unused forevermore.

I would like to implement a solution that simply grants a new session token for anonymous users with an expired token. It seems as though I must set a finite session length, and I would rather not just set it to 100 years - it does not solve the problem for existing users and feels like a hack.

Thank you for your help.

Kind regards,
Alex

Steps to reproduce

On the client, initialise Parse with:

[Parse initializeWithConfiguration:[ParseClientConfiguration configurationWithBlock:^(id<ParseMutableClientConfiguration> _Nonnull configuration) { 
    configuration.applicationId = <app id>; 
    configuration.clientKey = <client key>; 
    configuration.server = <server>; }]];

// Users 
[PFUser enableRevocableSessionInBackground]; 
[PFUser enableAutomaticUser];
[PFACL setDefaultACL:[PFACL ACL] withAccessForCurrentUser:YES];

On the server use:

Expire inactive session: YES
Revoke session on password reset: YES
Session Length: <Finite Time>
Enable Anonymous Authentication: NO or YES...

Anonymous automatic users are created for new users. They work as expected during their valid lifetime. After an automatic PFUser has existed for "Session Length" the session token expires.

Expected Results

Given that the user did not enter any credentials and that automatic user is enabled, I would have expected Parse Server to grant a new valid (revocable) session token automatically.

Actual Outcome

Client-server interactions fail with error code 209: "Session token is expired.", 101, or 206. 101 is clearly related to the invalid Session Token. 206 may or may not be a related issue.

The client is effectively cut adrift from our back-end and cannot synchronise with our database or interact with our server.

They cannot be asked to log in, as they have no credentials with which to log in. Currently it seems they are forced to reinstall the app and become a new anonymous user.

Environment Setup

  • Server

    • parse-server version (Be specific! Don't say 'latest'.) : 2.3.2
    • Operating System: Unknown
    • Hardware: Nodechef Hosted
    • Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): Remote Nodechef
  • Database

    • MongoDB version: 3.0.12
    • Storage engine: Nodechef Hosted
    • Hardware: Nodechef Hosted
    • Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): Remote Nodechef

Logs/Trace

Initialisation

--- Configure Facebook Authentication --
--- Enable Anonymous Authentication --
--- Allow Client Class Creation Disabled ---
--- Expire Inactive Sessions Enabled ---
--- Revoke Session On Password Reset Enabled ---
--- Session length in seconds: 31536000 ---

Example 209
Session token is expired.

Example 206

Error generating response. ParseError
{
"code": 206,
"message": "Cannot modify user k6uCHupaxA."
}

Cannot modify user k6uCHupaxA.

Example 101

Error handling request: ParseError
{
"code": 101,
"message": "invalid session"
}

invalid session

@flovilmart
Copy link
Contributor

Hi @AlexGingell this is a known issue for the anonymous sessions. They will effectively expire after their expiration time. That would be interesting to either:

  • regenerate a session for the users OR
  • extending the validity of the current session token.

Given the current state of the SDK's, the latter solution would be probably the one that is more suited.
Also that make me think that we should probably extend the sessions when they are in use. (once a day or once every 10 minutes etc...)

What do you think?

@AlexGingell
Copy link
Author

Hi @flovilmart

Thank you for the incredibly fast response. I'm glad it's not because I'm doing something silly.

The two solutions you propose seem equivalent in terms of user experience.

I assume by "regenerate a session" you mean you would create a new session token / session for the user. In this case, it would be preferable to me to simply extend the validity of the current token, because it would produce less clutter in the database, avoiding a proliferation of invalid tokens/sessions.

The "Expire inactive session" server setting - does this not do anything then? What constitutes inactivity? At face value this makes it seem like the session would expire if the user didn't perform any operation for a set time period, but there is nowhere to set that time period and it may be vestigial.

Extending the sessions when they are in use would be fine, but if the user puts down the app for 2 years and then picks it up again, we would want their automatic user session token to extend even if it had become invalid in the interim.

What do you think should be done? This seems like a high priority given that the default session length is 1 year and Parse Server has been around for longer than this now.

@flovilmart
Copy link
Contributor

This seems like a high priority given that the default session length is 1 year and Parse Server has been around for longer than this now.

Yes this is something that need to be addressed, but, if you do not plan to have expiring sessions, you can very well for now set all your sessions expiration dates in a distant future right from mongodb.
Otherwise I can probably check for a workaround for those particular automatic sessions.

@AlexGingell
Copy link
Author

AlexGingell commented May 31, 2018

@flovilmart I would certainly appreciate any help you could give me.

I have around 3.18 million sessions in our database and, frankly, I'm not used to editing them directly and I'm afraid to break anything!

I am heartened by your responses though, and looking forward to solving this issue for our users.

Is the session length used dynamically in the sense that a users session expiry date is updated each time they log in, or is the expiry date set on creation of the anonymous PFUser? I'm just wondering if I update the session length to 3 years (for example) if that will only apply to new PFUsers, or would extend the expiry date for existing users.

@flovilmart
Copy link
Contributor

The session expiration is a date set on the expiresAt field. So you’re right, setting to 3 years affects all users. I’ll try to work on a workaround that makes the anonymous users sessions with expanding expiration date

@AlexGingell
Copy link
Author

AlexGingell commented May 31, 2018

@flovilmart but is the expiresAt field set only on creation (using the session length at time of creation) or is it something that is updated dynamically every time the PFUser is saved (for example)?

I have migrated to Parse Server v2.6.2 and extended session length to three years, but I still see these invalid session token logs.

This suggests that new users are going to be just fine for 3 years, but all previous users are still marching towards (or beyond) their 1 year expiresAt limit.

In fact, since moving to v2.6.2 I notice additional new "bad request" errors that were not present in 2.3.2:

 2018-05-31 16:50:06	App1 Error generating response. ParseError
{
  "code": 206,
  "message": "Cannot modify user 9a3VKIf8dn."
}

Cannot modify user 9a3VKIf8dn. 
Error generating response. ParseError
{
  "code": 206,
  "message": "Cannot modify user 9a3VKIf8dn."
}

Cannot modify user 9a3VKIf8dn.
 2018-05-31 16:49:21	App1 Session token is expired.
 2018-05-31 16:49:20	App1 Session token is expired.
 2018-05-31 16:49:19	App1 Session token is expired.
 2018-05-31 16:49:17	App1 Session token is expired.
 2018-05-31 16:49:06	App1 BadRequestError: request aborted 
at IncomingMessage.onAborted (/bundle/node_modules/raw-body/index.js:231:10) 
at emitNone (events.js:86:13) 
at IncomingMessage.emit (events.js:185:7) 
at abortIncoming (_http_server.js:281:11) 
at Socket.serverSocketCloseListener (_http_server.js:294:5) 
at emitOne (events.js:101:20) 
at Socket.emit (events.js:188:7) 
at TCP._handle.close [as _onclose] (net.js:497:12)

I can try moving to v2.7.2 - just wondering which version of node you recommend for it?

@flovilmart
Copy link
Contributor

First you should probably move to 2.8.1, as 2.6.2 is quite old. Also, as I mentioned; this is expected, the expiresAt is set only once, on creation and never updated.

@AlexGingell
Copy link
Author

AlexGingell commented May 31, 2018

@flovilmart Understood - I was confused when you said "So you’re right, setting to 3 years affects all users."
I suppose that, as you suggested, I can look to find a safe way to manually set all session expiresAt fields to 3 years from today, while a solution is worked on.

Nodechef (who we're hosted with) only support up to v2.7.2 at this time. We're now on Node.js v6.11.1 and Parse v2.6.2.

I see that Parse Server 2.7 requires at least Node 6.11.4. We only have the option of 6.11.1 or moving up to between 7.0 and 8.9.0. Do you recommend a particular version of node for Parse 2.7.2 and above? Thank you for your excellent support!

@flovilmart
Copy link
Contributor

I would recommend to stick to the latest version as we provide bug fixes only for the latest versions. Also, it is known that nodechef runs a fork of parse-server so I can’t speak to the compatibility with the open source project.

Node 6 is retired and not supported anymore, the LTS version is 8.10. You should update it as well (or let nodechef know that it’s oudated)

@AlexGingell
Copy link
Author

@flovilmart Thank you for the advice. I've managed to get us to Node.js v7.10.1 and Parse v2.7.2.

I know these are both outdated, but it's a start. I will contact Nodechef, and in the meantime, explore the possibility of resetting all expiresAt fields manually to mitigate token problems until your solution is applicable.

I'll monitor this and be on hand to help if I can. I appreciate your time and effort - thank you! :)

@flovilmart
Copy link
Contributor

Yep, you should ask NodeChef for that or directly from mongodb.

@AlexGingell
Copy link
Author

AlexGingell commented May 31, 2018

@flovilmart I just updated all our sessions giving them an expiresAt date in 2021, and I can confirm that this appears to fix the error code 209 issue (or at least works around it until a solution is implemented in Parse Server). I was working with users with this issue, and the second I did that, all the problems righted themselves and they were able to synchronise with our database. I haven't seen a "Session token is expired" error since doing that.

We still seem to get some "invalid session token" and

Error handling request: ParseError
{
  "code": 101,
  "message": "invalid session"
}

invalid session

errors in the logs. We also still see:

Error generating response. ParseError
{
  "code": 206,
  "message": "Cannot modify user KlL6zbXkSU."
}

Cannot modify user KlL6zbXkSU. 

and

BadRequestError: request aborted 
at IncomingMessage.onAborted (/bundle/node_modules/body-parser/node_modules/raw-body/index.js:231:10) 
at emitNone (events.js:86:13) 
at IncomingMessage.emit (events.js:188:7) 
at abortIncoming (_http_server.js:381:9) 
at socketOnClose (_http_server.js:375:3) 
at emitOne (events.js:101:20) 
at Socket.emit (events.js:191:7) 
at TCP._handle.close [as _onclose] (net.js:511:12)

This last error only started appearing today after I migrated from Parse Server v2.3.2 / Node.js v6.11.1. I'm tempted to downgrade and see if this goes away.

We have about 45k DAU today, and I'm seeing these bad requests or 'cannot modify user' errors every 5-10 minutes or so in the logs.

EDIT:

I downgraded to Parse v2.3.2 and Node.js v6.11.1. I no longer see BadRequestError request aborts, though 101, 206 errors still persist. I realise we're now off-topic, but it's not clear why users can't be modified or sessions are invalid.

@claesjacobsson
Copy link

This was a very useful thread, thanks!

Alex, you wrote that you updated all your sessions. Did you update the mongodb directly or how?

@AlexGingell
Copy link
Author

AlexGingell commented Oct 4, 2018 via email

@claesjacobsson
Copy link

Thanks for your quick response!

I have the same situation. I have no real experience with manipulating the prod db directly, but it was pretty straightforward?

@AlexGingell
Copy link
Author

AlexGingell commented Oct 4, 2018 via email

@claesjacobsson
Copy link

Sound great! Thanks a lot, I really appreciate it :-)

@funkenstrahlen
Copy link
Contributor

Great conversation. Helped me a lot today!

@tblank555
Copy link

@AlexGingell Thank you for the incredibly detailed description of your problem! Reading through it helped me realize a bit more about what is going on in my own app, as I'm seeing pretty much the exact same issue.

To me, it seems like the appropriate thing for a client to do when its authentication token has expired is to request a new one. You pointed out that anonymous users are a somewhat different case, and that prompting a user to "log in again" when they never logged in in the first place does not seem like appropriate behavior, and I agree. So, it seems like what should happen is the same thing that happened when they first launched your app: a session token was requested and granted freely by the server without the requirement of login credentials.

But I suppose there's a bit of a security concern in that behavior though. Consider this scenario:

  1. I'm a new user of your app, and I download it, and log in as an anonymous user.
  2. Your server grants me a user account and session/auth token that allows me to use your app for 1 year.
  3. During that year, I use your app, and in doing so, I associate personal data with my account.
    Once that year has passed and my token has expired, my app can no longer communicate with the server, because it needs to authenticate again.
  4. Now Mr. Hackerman 5000 appears on the scene, and he manages to request a new token for my anonymous account.
  5. Since my account is anonymous, it has no way of determining who should rightfully have access to it, so the server grants Mr. Hackerman 5000 access to my account, and now all of my sensitive personal data is in the hands of some dude named Mr. Hackerman 5000.

Does anyone know what the interaction of requesting a session token for an anonymous user looks like? Is there any way that refreshing a token for an anonymous user could work so that only the user that requested the first token receives the new one?

@funkenstrahlen
Copy link
Contributor

Just as a note: You can also add expireInactiveSessions: false to the server configuration to stop sessions from expiring at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants