Reputation: 1628
I am trying to write a simple (hah!) Python Flask app which will use Google's people
API to retrieve a user's numeric Google ID (which otherwise seems to be almost impossible to determine). Doing this requires that the user authenticate with Google's OAUTH. I have this working, but Google's Using OAuth 2.0 for Web Server Applications documentation says:
The OAuth client must prevent CSRF as called out in the OAuth2 Specification . One way to achieve this is by using the state parameter to maintain state between your authorization request and the authorization server's response.
So I'm following this example, and storing the state in a session variable.
@app.route('/auth')
def auth():
auth_url, state = get_oauth_flow().authorization_url()
app.logger.debug(f'Setting session state: {state}')
flask.session['state'] = state
return flask.redirect(auth_url)
@app.route('/oauth2callback')
def oauth2callback():
session_state = flask.session.get('state')
request_state = flask.request.args.get('state')
app.logger.debug(f'Got session state: {session_state}')
app.logger.debug(f'Got request state: {request_state}')
if session_state is None or session_state != request_state:
return 'Danger, Will Robinson!', 400
del flask.session['state']
flow = get_oauth_flow()
flow.fetch_token(authorization_response=flask.request.url)
flask.session['oauth_token'] = flow.credentials.token
return flask.redirect('success')
This is failing whenever I go through the initial login process. (I receive a "Danger, Will Robinson!" message.) My logs show that the session variable has been cleared.
DEBUG in ging: Setting session state: ijV2BAyuZG8uSO4rpN77nczw5UDEJf
DEBUG in ging: Got session state: None
DEBUG in ging: Got request state: ijV2BAyuZG8uSO4rpN77nczw5UDEJf
I'm not a web developer by trade, so I'm very much at sea here. Any suggestions on what I might be doing wrong and/or other approaches would be appreciated.
Upvotes: 0
Views: 22
Reputation: 1628
I'm 99% sure that I figured out what the problem is. I don't like the idea of putting a static secret key (for Flask session variables) anywhere, so I was doing this.
app = flask.Flask(__name__)
app.secret_key = os.urandom(24)
This worked just fine when I was just running my Flask app from the command line, but it failed sporadically when running with Apache and mod_wsgi
. It finally hit me that Apache is spawning multiple processes for my app, and each one of those ends up with its own secret key, which can't decrypt a session cookie set by another process. (Some sort of warning when Flask fails to decrypt a session cookie would be really nice.)
The usual approaches to this problem involves saving the key in a file somewhere or setting an environment variable — presumably with a hardcoded value — which brings me right back to the static key that I want to avoid. (I should note here that the app I'm working on is extremely simple, so I really don't care whether sessions survive a restart.)
I ultimately settled on this approach.
app = flask.Flask(__name__)
# This file should be created by the httpd startup script/service
with open('/tmp/ging-session-key', 'rb') as gsk:
app.secret_key = gsk.read()
I added the following drop-in to my httpd.service
definition to create the file whenever it starts.
[Service]
ExecStartPre=/usr/bin/dd if=/dev/urandom of=/tmp/ging-session-key bs=24 count=1
ExecStartPre=/usr/bin/chown apache:apache /tmp/ging-session-key
ExecStartPre=/usr/bin/chmod 0400 /tmp/ging-session-key
ExecStartPre=/usr/bin/chcon -t httpd_tmp_t /tmp/ging-session-key
The OS-provided unit file already includes PrivateTmp=true
, so this should be relatively secure.
Upvotes: 0