Redis as a session store for webapp, Invalidate all of a user's session.

646 views
Skip to first unread message

Gautam Kumar

unread,
Feb 6, 2015, 6:07:22 AM2/6/15
to redi...@googlegroups.com
I'm trying to use redis as a session store, 

We are storing sessions like so

[NameSpace]:[UniqueId] -> [email_id]

Here is the problem, 
when a user resets their password, how do we invalidate all the sessions of that user ? 

Here are the solutions I came up with, 

Store email id as part of UID

Store sessions like so
[NameSpace]:[UniqueId]-[email_id] -> [email_id]

Then I can use SCAN MATCH to delete all the keys when the user resets the password.

Maintain a list of UIDs

After storing sessions like
[NameSpace]:[UniqueId] -> [email_id]

Maintain a separate list with 
[NameSpace2]:[email_id] -> [ "[UniqueId]", "[UniqueId]" ]

and invalidate the sessions using the list.

My question is what is the recommended way to do session invalidation in redis ?

Itamar Haber

unread,
Feb 6, 2015, 10:41:47 AM2/6/15
to redi...@googlegroups.com
It really depends on what you want to achieve - there's always a trade off between CPU and RAM so you'll end up paying somehow no matter what.

I assume that bulk-invalidation isn't a common operation compared to regular session start/end events. By using the SCAN approach you'll mainly be paying with CPU (and consequently time) - so if you're strapped for RAM and/or don't care that bulk session invalidation isn't "instant", you can use it but keep in mind that this is a potential scaling barrier. I would, therefore, usually recommend the other option.

Given that the number of user-specific active sessions is assumed to be much lower than the total number of active sessions in the database and if you want to invalidate quickly and efficiently, go with maintaining the list of active sessions per user instead of SCANning the entire keyspace. The cost of doing that is the extra RAM taken by that "index" as well as the extra operations you'll be doing to maintain it. 

Instead of using a List, however, you should consider using a Set. Sets automatically take care of ensuring active session uniqueness and are more efficient when checking for member existence (compared to a List). With Sets you can also choose between using SMEMBERS or SSCAN when you need all of the active sessions (e.g. for bulk invalidation). The extra complexity of adding/removing to a Set (O(log(N)) compared to managing a List (O(1) and (O(N), respectively) is negligible in the majority of login/logout events.

--
You received this message because you are subscribed to the Google Groups "Redis DB" group.
To unsubscribe from this group and stop receiving emails from it, send an email to redis-db+u...@googlegroups.com.
To post to this group, send email to redi...@googlegroups.com.
Visit this group at http://groups.google.com/group/redis-db.
For more options, visit https://groups.google.com/d/optout.



--

Itamar Haber | Chief Developers Advocate
Redis Watch Newsletter - Editor and Janitor
Redis Labs - Enterprise-Class Redis for Developers

Mobile: +1 (415) 688 2443
Mobile (IL): +972 (54) 567 9692
Email: ita...@redislabs.com
Skype: itamar.haber

Blog  |  Twitter  |  LinkedIn


Gautam Kumar

unread,
Feb 6, 2015, 11:14:21 AM2/6/15
to redi...@googlegroups.com
Thanks, I get it. I'll got speak to the team about this. I'm guessing we probably need to do an initial solution now and then think about further scaling a bit later.

I'm hesitant about choosing the second option because, we expire our sessions in 24 hrs so I need to maintain the validity of the set or else it might just overflow with values, probably with pubsub. This is would be a lot more code with more potential for failure, bugs and security holes.

Garry Shutler

unread,
Feb 6, 2015, 11:23:51 AM2/6/15
to redi...@googlegroups.com
If you're already expiring after a fairly short period, there's another option.

Store, perhaps in Redis, a second value for the user other than email_id that gets changed when someone resets their password. Then, your redis key becomes namespace:other_key:uniqueid. Therefore, when other_key is changed by a reset, no code will be able to generate the complete key for other sessions and they'll expire 24 hours later on their own.

Gautam Kumar

unread,
Feb 7, 2015, 9:57:20 PM2/7/15
to redi...@googlegroups.com
Hi Garry,

So what you're suggesting is, I create a random key for that user and use that as part of the namespace. The random key can probably be stored in mysql, postgres or some other persistent storage. 
So when the password is reset the random key is merely regenerated so all sessions are invalidated and they expire eventually. 

That actually sounds like a brilliant idea, Let me see try it out.

Josiah Carlson

unread,
Feb 8, 2015, 1:28:42 AM2/8/15
to redi...@googlegroups.com
If you can query your relational database for a mapping from email (or other login) -> random id, you can just store that same email -> random id lookup in Redis.


I was going to describe an alternate way that uses timestamps in Redis, but it was a lot faster to write the code in Python than it was to describe it linguistically, so here you go:

import time
import uuid

def login_user(rconn, email, password):
    if not password_correct(email, password):
        return False
    token = str(uuid.uuid4())
    now = int(time.time())
    rconn.setnx('logout_before:' + email, now)
    rconn.set(':' + token, '%s\0%s'%(now, email))
    set_login_cookie(token)
    return True
    
def auto_login_valid(rconn, token):
    data = rconn.get(':' + token)
    if not data:
        return False
    ts, _, email = data.partition('\0')
    before = rconn.get('logout_before:' + email)
    try:
        if int(ts) >= int(before):
            return False
    except:
        return False
    return True

def logout_user(rconn, token):
    rconn.delete(':' + token)

def logout_others(rconn, token):
    data = rconn.get(':' + token)
    if not data:
        raise Exception("token no longer in Redis")
    email = data.partition('\0')[-1]
    now = int(time.time())
    rconn.set('logout_before:' + email, now)
    rconn.set(':' + token, '%s\0%s'%(now, email))
    set_login_cookie(token)


You don't get a list of others logged in with a given email, and you don't get auto-expiration of the auto-login, but both are easy to add.

 - Josiah


Garry Shutler

unread,
Feb 9, 2015, 4:05:26 AM2/9/15
to redi...@googlegroups.com
Yep, precisely that. Though you could store that value in Redis under something like session_key:email_id if you wanted. If you're already hitting your persistent store to get other things it's not clear to me which would be better though as it would change infrequently.

Emil Vaagland

unread,
Feb 23, 2015, 4:13:31 AM2/23/15
to redi...@googlegroups.com
You can store the session id's for each user in a separate zSet with a timestamp as score. The score timestamp will act as a time to live for each member in the set if you call ZREMRANGEBYSCORE(key, 0, time()).

Example, Redis commands to store a session with TTL and maintain a zSet for reverse lookup on user_id:

SETEX" "session:session_id" TTL

ZREMRANGEBYSCORE" "session:u:user_id" 0 time()

"ZADD" "session:u:user_id" time()+TTL "session:session_id"

"EXPIRE" "session:u:user_id" TTL

All sessions that are expired (score between 0 and time()) will be deleted by ZREMRANGEBYSCORE.
Reply all
Reply to author
Forward
0 new messages