Skip to content

Commit f591123

Browse files
authored
Merge pull request #6 from rlam3/master
Adding read the docs
2 parents ebef035 + ba5d889 commit f591123

18 files changed

+1312
-6
lines changed

__init__.py

Whitespace-only changes.

app.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
from datetime import timedelta
2+
3+
import simplekv
4+
import simplekv.memory
5+
from flask import Flask, request, jsonify
6+
7+
from flask_jwt_extended import JWTManager, jwt_required, fresh_jwt_required,\
8+
create_refresh_access_tokens, create_fresh_access_token, refresh_access_token,\
9+
jwt_identity, jwt_claims, revoke_token, unrevoke_token, get_stored_tokens
10+
11+
# Example users database
12+
USERS = {
13+
'test1': {
14+
'id': 1,
15+
'password': 'abc123',
16+
'type': 'restricted'
17+
},
18+
'test2': {
19+
'id': 2,
20+
'password': 'abc123',
21+
'type': 'admin'
22+
},
23+
}
24+
25+
# Flask test stuff
26+
app = Flask(__name__)
27+
app.debug = True
28+
app.secret_key = 'super-secret'
29+
30+
# Optional configuration options for flask_jwt_extended
31+
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1) # defaults to 15 minutes
32+
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=7) # defaults to 30 days
33+
app.config['JWT_ALGORITHM'] = 'HS512' # Default to HS256
34+
35+
# Enable JWT blacklist / token revoke
36+
app.config['JWT_BLACKLIST_ENABLED'] = True
37+
38+
# We are going to be using a simple in memory blacklist for this example. In
39+
# production, you will likely prefer something like redis (it can work with
40+
# multiple threads and processes, and supports automatic removal of expired
41+
# tokens so the blacklist doesn't blow up). Check here for available storage
42+
# options: http://pythonhosted.org/simplekv/
43+
blacklist_store = simplekv.memory.DictStore()
44+
app.config['JWT_BLACKLIST_STORE'] = blacklist_store
45+
46+
# Which tokens to check against the blacklist. Default is 'refresh'. Options are:
47+
# 'all': Check blacklist for access and refresh tokens
48+
# 'refresh': Check blacklist only for refresh tokens
49+
app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'refresh'
50+
51+
jwt = JWTManager(app)
52+
53+
54+
# Function to add your own data (claims) to the JWT (optional).
55+
@jwt.user_claims_loader
56+
def my_claims(identity):
57+
return {
58+
'type': USERS[identity]['type'],
59+
'ip': request.remote_addr
60+
}
61+
62+
63+
# Function to change the result if someone without a token tries to access a
64+
# protected endpoint (optional)
65+
@jwt.unauthorized_loader
66+
def my_unauthorized_response():
67+
return jsonify({
68+
'status': 401,
69+
'sub_status': 100,
70+
'message': 'You must submit a valid JWT to access this endpoint',
71+
})
72+
73+
74+
# Function to change the result if someone with an expired token tries
75+
# to access a protected endpoint (optional)
76+
@jwt.expired_token_loader
77+
def my_expired_response():
78+
return jsonify({
79+
'status': 401,
80+
'sub_status': 101,
81+
'message': 'Token expired',
82+
})
83+
84+
85+
# Endpoint for authing a user
86+
@app.route('/auth/login', methods=['POST'])
87+
def login():
88+
username = request.json.get('username', None)
89+
password = request.json.get('password', None)
90+
if username is None or password is None:
91+
return jsonify({"msg": "Missing username or password"}), 422
92+
93+
if username not in USERS:
94+
return jsonify({"msg": "Bad username or password"}), 401
95+
if USERS[username]['password'] != password:
96+
return jsonify({"msg": "Bad username or password"}), 401
97+
98+
return create_refresh_access_tokens(identity=username)
99+
100+
101+
# Endpoint for getting a fresh access token for a user
102+
@app.route('/auth/fresh-login', methods=['POST'])
103+
def fresh_login():
104+
username = request.json.get('username', None)
105+
password = request.json.get('username', None)
106+
if username is None or password is None:
107+
return jsonify({"msg": "Missing username or password"}), 422
108+
109+
if username not in USERS:
110+
return jsonify({"msg": "Bad username or password"}), 401
111+
if USERS[username]['password'] != password:
112+
return jsonify({"msg": "Bad username or password"}), 401
113+
114+
# TODO change all these to simply return the data, you are in charge or
115+
# getting it to your frontend
116+
return create_fresh_access_token(identity=username)
117+
118+
119+
# Endpoint for generating a non-fresh access token from the refresh token
120+
@app.route('/auth/refresh', methods=['POST'])
121+
def refresh_token():
122+
# TODO make this either a url that is configured in the app.config, or a
123+
# decorator, or something. This feels super awkward to use atm
124+
return refresh_access_token()
125+
126+
127+
# Endpoint for listing tokens
128+
@app.route('/auth/tokens', methods=['GET'])
129+
def list_tokens():
130+
# TODO you should put some extra protection on this, so a user can only
131+
# view their tokens, or some extra privillage roles so an admin can
132+
# view everyones token
133+
return jsonify(get_stored_tokens()), 200
134+
135+
136+
# Endpoint for revoking and unrevoking tokens
137+
@app.route('/auth/tokens/<string:jti>', methods=['PUT'])
138+
def change_jwt_revoke_state(jti):
139+
# TODO you should put some extra protection on this, so a user can only
140+
# modify their tokens
141+
revoke = request.json.get('revoke', None)
142+
if revoke is None:
143+
return jsonify({'msg': "Missing json argument: 'revoke'"}), 422
144+
if not isinstance(revoke, bool):
145+
return jsonify({'msg': "revoke' must be a boolean"}), 422
146+
147+
if revoke:
148+
revoke_token(jti)
149+
return jsonify({"msg": "Token successfully revoked"})
150+
else:
151+
unrevoke_token(jti)
152+
return jsonify({"msg": "Token successfully unrevoked"})
153+
154+
155+
@app.route('/protected', methods=['GET'])
156+
@jwt_required
157+
def non_fresh_protected():
158+
ip = jwt_claims['ip'] # Access data stored in custom claims on the JWT
159+
username = jwt_identity # Access identity through jwt_identity proxy
160+
161+
msg = '{} initially logged in at {}'.format(username, ip)
162+
return jsonify({'msg': msg})
163+
164+
165+
@app.route('/protected-fresh', methods=['GET'])
166+
@fresh_jwt_required
167+
def fresh_protected():
168+
user_type = jwt_claims['type'] # Access data stored in custom claims on the JWT
169+
username = jwt_identity # Access identity through jwt_identity proxy
170+
171+
msg = '(fresh token required) {} is a[n] {}'.format(username, user_type)
172+
return jsonify({'msg': msg})
173+
174+
if __name__ == '__main__':
175+
app.run()

0 commit comments

Comments
 (0)