-
Notifications
You must be signed in to change notification settings - Fork 541
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.
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'
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)
);
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
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.
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
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.