1- import binascii
2- import json
3- import os
4-
51from flask import Flask , jsonify , request
6- from flask import Response
72
83from flask_jwt_extended import JWTManager , jwt_required , create_access_token , \
9- jwt_refresh_token_required , create_refresh_token , get_jwt_identity
4+ jwt_refresh_token_required , create_refresh_token , get_jwt_identity ,\
5+ set_access_cookies , set_refresh_cookie
6+
7+
8+ # NOTE: This is being actively worked on, and is not complete yet. At present,
9+ # this code will not work! It should be rolled out next week sometime
10+
1011
1112app = Flask (__name__ )
1213app .secret_key = 'super-secret' # Change this!
1314jwt = JWTManager (app )
1415
1516
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
17+ # Configure application to store jwts in cookies with double submit csrf protection
18+ app .config ['JWT_TOKEN_LOCATION' ] = 'cookie'
19+ app .config ['JWT_COOKIE_HTTPONLY' ] = True
20+ app .config ['JWT_COOKIE_SECURE' ] = True
1921
20- def _create_xsrf_token ():
21- return binascii .hexlify (os .urandom (60 ))
22+ app .config ['JWT_ACCESS_COOKIE_NAME' ] = 'access_token_cookie'
23+ app .config ['JWT_ACCESS_COOKIE_PATH' ] = '/api/'
24+
25+ app .config ['JWT_REFRESH_COOKIE_NAME' ] = 'refresh_token_cookie'
26+ app .config ['JWT_REFRESH_COOKIE_PATH' ] = '/token/refresh'
27+
28+ app .config ['JWT_COOKIE_CSRF_PROTECT' ] = True
29+ app .config ['JWT_ACCESS_CSRF_COOKIE_NAME' ] = 'x_xsrf_access_token'
30+ app .config ['JWT_REFRESH_CSRF_COOKIE_NAME' ] = 'x_xsrf_refresh_token'
2231
2332
2433@app .route ('/token/auth' , methods = ['POST' ])
@@ -28,95 +37,27 @@ def login():
2837 if username != 'test' and password != 'test' :
2938 return jsonify ({"msg" : "Bad username or password" }), 401
3039
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' )
40+ # Create the tokens we will be sending back to the user
41+ access_token = create_access_token (identity = username )
42+ refresh_token = create_refresh_token (identity = username )
8843
44+ # Set the JWTs and the CSRF double submit protection cookies in this response
45+ resp = jsonify ({'login' : True }), 200
46+ set_access_cookies (resp , access_token )
47+ set_refresh_cookie (resp , refresh_token )
8948 return resp
9049
9150
9251@app .route ('/token/refresh' , methods = ['POST' ])
9352@jwt_refresh_token_required
9453def 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 }
54+ # Create the new access token
10055 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/' )
56+ access_token = create_access_token (identity = current_user )
11957
58+ # Set the access JWT and CSRF double submit protection cookies in this response
59+ resp = jsonify ({'refresh' : True }), 200
60+ set_access_cookies (resp , access_token )
12061 return resp
12162
12263
0 commit comments