Skip to content

Session pseudo code

Rishabh Poddar edited this page Oct 3, 2020 · 10 revisions

The logic below is not a one-to-one mapping with the actual code. For example, the pseudo-code below does not optimise on number of db lookups, and it does not express the use of transactions for thread safety.

Notation

encrypt(x, y) = encrypt 'x' using the key 'y'
createJWTWithPayload(x, y) = create a JWT containing 'x' as the payload, signed with 'y'
hash(x) = hash of 'x'
base64(x) = base64 representation of 'x'

Tables

CREATE TABLE refresh_tokens (
    session_handle VARCHAR(255) NOT NULL,
    user_id VARCHAR(128) NOT NULL,
    refresh_token_hash_2 VARCHAR(128) NOT NULL,
    session_data TEXT,
    expires_at BIGINT UNSIGNED NOT NULL,
    created_at_time BIGINT UNSIGNED NOT NULL,
    jwt_user_payload TEXT,
    PRIMARY KEY(session_handle)
);

Creating a session

Input from user:
userId: string
jwtPayload: JSON object, array or any primitive type. Is serialised everywhere.
sessionData: JSON object, array or any primitive type. Is serialised everywhere.
res: response object

Logic:
sessionHandle = a random string that will stay constant during this session
jwtKey = fetched from memory / db
refreshTokenKey = fetched from memory / db
refreshNonce = a random string
antiCsrfToken = a random string
timeNow = time in milli now.

refreshToken = encrypt({sessionHandle, userId, refreshNonce}, refreshTokenKey) + "." + refreshNonce
accessToken = createJWTWithPayload({sessionHandle, userId, rt: hash(refreshToken), expiryTime, userPayload: jwtPayload, antiCsrfToken}, jwtKey)
frontToken = base64({uid: userId, ate: expiryTime, up: jwtPayload})
idRefreshToken = a random string

insert into db: sessionHandle => {userId, rtHash2: hash(hash(refreshToken)), sessionData, refreshTokenExpiryTime, timeNow, jwtPayload}
    
set refreshToken (HttpOnly, secure), idRefreshToken (HttpOnly, secure) and accessToken (HttpOnly, secure) in cookies using res
set antiCsrfToken in the res header with the key anti-csrf
set idRefreshToken in res header with the key id-refresh-token
set frontToken in res header with key front-token
    
return

Getting session from cookie

Input from user:
req: request object. Should contain the accessToken & idRefreshToken cookies, and antiCsrf header if enabled
res: response object 
enableAntiCsrf: boolean

Logic:
jwtKey = fetched from memory / db

if idRefreshToken cookie is missing:
    clear cookies and throw UNAUTHORISED

if accessToken cookie is missing:
    throw TRY_REFRSH_TOKEN 
    
accessToken = fetch from cookies using req object.
accessTokenInfo = verifyJWTAndGetPayload(accessToken, jwtKey) // if this fails or token has expired, we throw TRY_REFRESH_TOKEN error

if enableAntiCsrf
    tokenFromHeader = get anti-csrf header value from res
    if tokenFromHeader == undefined || tokenFromHeader !== accessTokenInfo.antiCsrfToken
        throw TRY_REFRSH_TOKEN


needsToUpdateJWTPayload = false
if blacklisting is enabled
    if accessTokenInfo.sessionHandle is not in database
        clear cookies and throw UNAUTHORISED
    if accessTokenInfo.userPayload != payload from the database:
        needsToUpdateJWTPayload = true


if accessTokenInfo.prt === undefined && !needsToUpdateJWTPayload
    return session object to user using accessTokenInfo.


// please see Refresh a session section below first. Otherwise, the logic below will not make much sense.
sessionInfo = read session row from db using accessTokenInfo.sessionHandle
if sessionInfo === undefined
    clear cookies and throw UNAUTHORISED

if sessionInfo.rtHash2 === hash(accessTokenInfo.prt) || sessionInfo.rtHash2 === hash(accessTokenInfo.rt) || needsToUpdateJWTPayload
    if sessionInfo.rtHash2 === hash(accessTokenInfo.prt)
        update db row: sessionInfo.sessionHandle => {rtHash2: hash(accessTokenInfo.rt), now + refreshTokenExpiryTime}

    newAccessToken = create new JWT using input accessTokenInfo. Get the JWTPayload from sessionInfo, and set prt to null.
    update cookies with new access token using res object.
    return session object to user.
    
return session object to user using accessTokenInfo.

WORK IN PROGRESS BELOW---------------

Refreshing a session

Input from user:
req: express request object. Should only contain the refreshToken and idRefresh cookies
res: express response object 

Logic:
refreshTokenKey = fetched from memory / db
jwtKey = fetched from memory / db

if auth cookies are missing:
    clear cookies and throw UNAUTHORISED

refreshToken = get token from req object
refreshTokenInfo = verifyAndGetInfo(refreshToken, refreshTokenKey) // if this fails, clear cookies and throw UNAUTHORISED error

<LABEL>
sessionHandle = refreshTokenInfo.sessionHandle
sessionInfo = read session row from db using sessionHandle

if sessionInfo === undefined || sessionInfo.refreshTokenExpiryTime < now || sessionInfo.userId !== refreshTokenInfo.userId
    clear cookies and throw UNAUTHORISED

if sessionInfo.rtHash2 === hash(hash(refreshToken))
    refreshNonce = a random string
    antiCsrfToken = a random string

    newRefreshToken = encrypt({sessionHandle, userId, refreshNonce, prt: hash(refreshToken)}, refreshTokenKey) + "." + refreshNonce
    newAccessToken = createJWTWithPayload({sessionHandle, userId, rt: hash(newRefreshToken), prt: hash(refreshToken) expiryTime, sessionInfo.jwtPayload, antiCsrfToken}, jwtKey)

    set cookies to these new tokens using res object.
    set anti-csrf header to the new antiCsrfToken.
    return session object to user.
    
if refreshTokenInfo.prt !== undefined && sessionInfo.rtHash2 === hash(refreshTokenInfo.prt)
    update db row: sessionInfo.sessionHandle => {rtHash2: hash(hash(refreshToken)), refreshTokenExpiryTime}
    GOTO <LABEL>

call token theft function
clear cookies and throw UNAUTHORISED error

Destroying a session

Input from user:
req: express request object. Should only contain the refreshToken and idRefresh cookies
res: express response object

Logic:
get session from cookie (above logic)
sessionHandle = get it from the session object.
delete db row where sessionHandle = sessionHandle
clear cookies if a row was deleted.

Regeneration of a session