Skip to content

Commit f1e7b3d

Browse files
committed
Add CSRF protection using state to python-flask
1 parent ac02d88 commit f1e7b3d

File tree

1 file changed

+23
-5
lines changed

1 file changed

+23
-5
lines changed

Diff for: python-flask/main.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from flask import Flask, render_template, request, session, redirect
1+
from flask import Flask, render_template, request, session, redirect, make_response
22
import os, sys
33
import requests
44
import jwt
5+
import uuid
56

67
CLIENT_ID = 'valtech.idp.testclient.local'
78
CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
@@ -27,12 +28,27 @@ def index():
2728
@app.route('/sign-in')
2829
def sign_in():
2930
if session.get('signed_in') != None: return redirect('/')
30-
authorize_url = 'https://stage-id.valtech.com/oauth2/authorize?response_type=%s&client_id=%s&scope=%s' % ('code', CLIENT_ID, 'email openid')
31-
return redirect(authorize_url)
31+
32+
# state is used for CSRF protection. the client generates a value and stores it
33+
# for the user somewhere (in a cookie or in a session). it then passes the same value
34+
# in the state parameter in the authorize request. IDP will mirror the state value
35+
# to the redirect URI. the client should then make sure the state value it has stored
36+
# matches what it receives in the callback
37+
state = str(uuid.uuid4())
38+
39+
authorize_url = 'https://stage-id.valtech.com/oauth2/authorize?response_type=%s&client_id=%s&scope=%s&state=%s' % ('code', CLIENT_ID, 'email openid', state)
40+
41+
resp = make_response(redirect(authorize_url))
42+
resp.set_cookie('python-flask-csrf', state)
43+
return resp
3244

3345
@app.route('/sign-in/callback')
3446
def sign_in_callback():
3547
code = request.args.get('code')
48+
state = request.args.get('state')
49+
50+
if state != request.cookies.get('python-flask-csrf'):
51+
raise Exception("Possible CSRF detected (state does not match stored state)")
3652

3753
# as both scope openid and email was requested on authorize request above, the client
3854
# will receive both an access_token (according to OAuth 2) AND an id_token (according to OpenID Connect)
@@ -49,8 +65,10 @@ def sign_in_callback():
4965

5066
session['signed_in'] = True
5167
session['email'] = user_info['email']
52-
53-
return redirect('/')
68+
69+
resp = make_response(redirect('/'))
70+
resp.set_cookie('python-flask-csrf', '', expires=0)
71+
return resp
5472

5573
@app.route('/sign-out')
5674
def sign_out():

0 commit comments

Comments
 (0)