Flask Session - csrgxtu/Cocoa GitHub Wiki

how flask implement session?

What's the basic principle of session?

As we know, HTTP is stateless, so in web development, if a user wants to get his or her private information, there is no guarantee that the user will get it right or the private information stays private. for example, if user B knows the id of user A's order id, then user B will use this id query the server and will get the data that shouldn't been seen by user B.

To solve this problem, web developers came up with session. when a user first login correct, the web applications will generate a id, and store the id as key with the user private information(user login info, i.e. user info) as the value stored temporarily on the server(temp file, memory, database, Redis etc). and will return this id to client. then client need to carry this id in the following request, and web applications will first retrieve the id and check if the corresponding user info in the temp storage, if yes, then it is a secure request, otherwise, need the client login. normally the id is returned to client through HTTP header Set-Cookie and the id carried to server is through request.Cookies.

How it implement generally?

When enabled the session in your web application, after successful login, server will generate a unique string as the session key, and when return, will indicate the client set the Cookies by "Set-Cookie" HTTP command. and then client will store the id into the local storage. when client do the following request, it will carry the id in the HTTP header. when web application receives the request, first retrieve the id from request, and then check if the client has logged in and if the client have the rights to access the private resource.

How Flask do it?

from flask import Flask, session
import json


app = Flask(__name__)


@app.route('/user_info')
def user_info():
  user_info = session.top('user_info')
  return json.dumps(user_info, indent=2)


@app.route('/login/<username>')
def login(username):
  session['user_info'] = {
    'username': username,
    'avatar': 'http://t.com/7yf32f.jpg'
  }
  return '{} logged in'.format(username)


@app.route('/logout')
def logout():
  session.pop('user_info')
  return '{} logged out'.format(username)


# secret_key
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'


if __name__ == '__main__':
  app.run()
# try to get user info without session
http GET http://localhost:5000/user_info
HTTP/1.0 200 OK
Content-Length: 14
Content-Type: text/html; charset=utf-8
Date: Tue, 08 Aug 2017 04:14:42 GMT
Server: Werkzeug/0.12.2 Python/2.7.6

not in session

# login with a username
http --session=/tmp/flask_session.json GET http://localhost:5000/login/archer
HTTP/1.0 200 OK
Content-Length: 16
Content-Type: text/html; charset=utf-8
Date: Tue, 08 Aug 2017 04:16:12 GMT
Server: Werkzeug/0.12.2 Python/2.7.6
Set-Cookie: session="9PhQ4YMRDwAcYjNyuZEyTeCslww=?user_info=KGRwMApTJ3VzZXJuYW1lJwpwMQpWYXJjaGVyCnAyCnNTJ2F2YXRhcicKcDMKUydodHRwOi8vdC5jb20vN3lmMzJmLmpwZycKcDQKcy4="; Path=/

archer logged in

# the session in JSON format
{
    "__meta__": {
        "about": "HTTPie session file",
        "help": "https://httpie.org/docs#sessions",
        "httpie": "0.9.9"
    },
    "auth": {
        "password": null,
        "type": null,
        "username": null
    },
    "cookies": {
        "session": {
            "expires": null,
            "path": "/",
            "secure": false,
            "value": "\"9PhQ4YMRDwAcYjNyuZEyTeCslww=?user_info=KGRwMApTJ3VzZXJuYW1lJwpwMQpWYXJjaGVyCnAyCnNTJ2F2YXRhcicKcDMKUydodHRwOi8vdC5jb20vN3lmMzJmLmpwZycKcDQKcy4=\""
        }
    },
    "headers": {}
}

# access the private user info with session
http --session=/tmp/flask_session.json GET http://localhost:5000/user_info
HTTP/1.0 200 OK
Content-Length: 66
Content-Type: text/html; charset=utf-8
Date: Tue, 08 Aug 2017 05:15:38 GMT
Server: Werkzeug/0.12.2 Python/2.7.6

{
    "avatar": "http://t.com/7yf32f.jpg",
    "username": "archer"
}

# logout and destroy the session
http --session=/tmp/flask_session.json GET http://localhost:5000/logout
HTTP/1.0 200 OK
Content-Length: 17
Content-Type: text/html; charset=utf-8
Date: Tue, 08 Aug 2017 05:19:48 GMT
Server: Werkzeug/0.12.2 Python/2.7.6
Set-Cookie: session="8lHyFtG/czKNFHuvsD4O7Z1ydoI=?"; Path=/

archer logged out

# the session data after logout
{
    "__meta__": {
        "about": "HTTPie session file",
        "help": "https://httpie.org/docs#sessions",
        "httpie": "0.9.9"
    },
    "auth": {
        "password": null,
        "type": null,
        "username": null
    },
    "cookies": {
        "session": {
            "expires": null,
            "path": "/",
            "secure": false,
            "value": "\"8lHyFtG/czKNFHuvsD4O7Z1ydoI=?\""
        }
    },
    "headers": {}
}

The upper is a demonstration of the session usage in Flask.

session data model

for each request context, there is a session global variable, as the following code snippet shows:

# context locals, flask line 662
_request_ctx_stack = LocalStack()
session = LocalProxy(lambda: _request_ctx_stack.top.session)

this session gloable variable only works in the current request thread, i.e, it also is a thread local data.

so if you do following in your web application, then you can manage session directly in your code.

from flask import session

# set session
session['user_info'] = {}

# remove session
session.pop('user_info')

process flow

when a client request /user_info api, it first comes to Werkzeug WSGI compatible web server. web server will prepare environ and start_response and call the Flask app.call, then goes to app.wsgi_app, in wsgi_app, do the following thing:

  • prepare request context
  • find the view function according to the request url
  • execute the view function and prepare response
  • use start_response return the results to the web server and then to the client
def wsgi_app(environ, start_response):
  with RequestContext(environ):
    rv = dispatch_request()
    response = make_response(rv)
    response = process_response(response)
    return response(environ, start_response)
load session data from request

when wsgi_app prepare the request context, it will open the session in the request, i.e load the session data in the request header:

class RequestContext(object):
  def __init__(self, app, environ):
    self.session = app.open_session(self.request)
    ...

def open_session(self, request):
  key = self.secret_key
  if key is not None:
    return SecureCookie.load_cookie(request, self.session_cookie_name, secret_key)

with the upper code, now RequestContext have a session data loaded from request header.

set session data into the response

when view function executed, now need to assemble the response, in wsgi, flask will invoke process_response, and process_response will set the response header with Set-Cookie.

def process_response(self, response):
  session = _request_ctx_stack.top.session
  if session is not None:
      self.save_session(session, response)
  return response

def save_session(self, session, response):
    if session is not None:
        session.save_cookie(response, self.session_cookie_name)

the overall flow

  • browser send request with session in it as Cookies
  • web server receives the request, and invoke the callable object of web application
  • start running of the web application, wsgi_app
  • prepare the request context first
    • load session data from request Cookies into request context
  • find the view function according to the requested url in url map
  • execute the view function with the arguments
  • prepare the response with the execute results
  • set the session data with HTTP Set-Cookie
  • return it to web server
  • return it to browser
⚠️ **GitHub.com Fallback** ⚠️