Skip to content

Commit ebef035

Browse files
committed
Example of what using a cookie for storage could look like
(refs #5)
1 parent f97b5f5 commit ebef035

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

examples/token_in_cookie.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import binascii
2+
import json
3+
import os
4+
5+
from flask import Flask, jsonify, request
6+
from flask import Response
7+
8+
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, \
9+
jwt_refresh_token_required, create_refresh_token, get_jwt_identity
10+
11+
app = Flask(__name__)
12+
app.secret_key = 'super-secret' # Change this!
13+
jwt = JWTManager(app)
14+
15+
16+
# TODO add additional_claims as optional arg to create_token methods
17+
# TODO config option to check for tokens in cookie instead of request headers (or both)
18+
# TODO config option to do xsrf double submit verification on protected endpoints
19+
20+
def _create_xsrf_token():
21+
return binascii.hexlify(os.urandom(60))
22+
23+
24+
@app.route('/token/auth', methods=['POST'])
25+
def login():
26+
username = request.json.get('username', None)
27+
password = request.json.get('password', None)
28+
if username != 'test' and password != 'test':
29+
return jsonify({"msg": "Bad username or password"}), 401
30+
31+
# Create the x-xsrf-token we will use for CSRF double submit verification
32+
x_xsrf_access_token = _create_xsrf_token()
33+
x_xsrf_refresh_token = _create_xsrf_token()
34+
access_claims = {'X-XSRF-TOKEN': x_xsrf_access_token}
35+
refresh_claims = {'X-XSRF-TOKEN': x_xsrf_refresh_token}
36+
37+
# Create the access and refresh tokens with the x-xsrf-token included
38+
access_token = create_access_token(identity=username,
39+
additional_claims=access_claims)
40+
refresh_token = create_refresh_token(identity=username,
41+
additional_claims=refresh_claims)
42+
43+
# Create the response we will send back to the caller.
44+
data = json.dumps({'login': True})
45+
resp = Response(response=data, status=200, mimetype="application/json")
46+
47+
# Save the access and refresh tokens in a cookie with this request.
48+
# The secure option insures that the cookie is only sent over https,
49+
# httponly makes it so javascript cannot access this cookie, and prevents
50+
# XSS attacks (we are still vulnerable to CSRF though), and path says to
51+
# only send this cookie if it matches the path. Using the path, we can have
52+
# access tokens only sent when we go to protected endpoints, and refresh
53+
# tokens only sent when we go to the refresh endpoint
54+
resp.set_cookie('access_token',
55+
value=access_token,
56+
secure=True,
57+
httponly=True,
58+
path='/api/')
59+
resp.set_cookie('refresh_token',
60+
value=refresh_token,
61+
secure=True,
62+
httponly=True,
63+
path='/token/refresh')
64+
65+
# Set the X-XSRF-TOKEN in a not httponly token (which can be accessed by
66+
# javascript, but only by javascript running on this domain). From here on
67+
# out, we will need to set the X-XSRF-TOKEN header for each request, getting
68+
# the xsrf token from this cookie. On the backend, we will be verifying the
69+
# xsrf token in the header matches the xsrf token in the JWT. The end result
70+
# of this is that attackers will not be able to perform CSRF attacks, as they
71+
# could send the JWT back with the request, but without the additional xsrf
72+
# header they will not get accepted, and they cannot access the xsrf token
73+
# as this cookie can only be accessed by javascript running from the same
74+
# domain (and the JWT is httponly and cannot be accessed by any javascript).
75+
# Additionally, the users access and refresh token can not be stolen via
76+
# XSS (again, because they are httponly), but XSS attacks could still be
77+
# used to perform actions for a user without stealing their cookie.
78+
resp.set_cookie('x_xsrf_access_token',
79+
value=x_xsrf_access_token,
80+
secure=True,
81+
httponly=False,
82+
path='/api/')
83+
resp.set_cookie('x_xsrf_refresh_token',
84+
value=x_xsrf_refresh_token,
85+
secure=True,
86+
httponly=False,
87+
path='/token/refresh')
88+
89+
return resp
90+
91+
92+
@app.route('/token/refresh', methods=['POST'])
93+
@jwt_refresh_token_required
94+
def refresh():
95+
# New xsrf token to use with the new jwt
96+
x_xsrf_token = _create_xsrf_token()
97+
98+
# Create the new jwt
99+
claims = {'X-XSRF-TOKEN': x_xsrf_token}
100+
current_user = get_jwt_identity()
101+
access_token = create_access_token(identity=current_user, additional_claims=claims)
102+
103+
# Create the respons to send back to the caller
104+
data = json.dumps({'refresh': True})
105+
resp = Response(response=data, status=200, mimetype="application/json")
106+
107+
# Set the JWT and XSRF TOKEN in the cookie with the same options and
108+
# security that we used for the original access token
109+
resp.set_cookie('access_token',
110+
value=access_token,
111+
secure=True,
112+
httponly=True,
113+
path='/api/')
114+
resp.set_cookie('x_xsrf_access_token',
115+
value=x_xsrf_token,
116+
secure=True,
117+
httponly=False,
118+
path='/api/')
119+
120+
return resp
121+
122+
123+
# We do not need to make any changes here, all of the protected endpoints will
124+
# function the exact same as they do when sending the jwt in via the authorization
125+
# header instead of in a cookie
126+
@app.route('/api/example', methods=['GET'])
127+
@jwt_required
128+
def protected():
129+
username = get_jwt_identity()
130+
return jsonify({'hello': 'from {}'.format(username)}), 200
131+
132+
if __name__ == '__main__':
133+
app.run()

0 commit comments

Comments
 (0)