-
Notifications
You must be signed in to change notification settings - Fork 3
/
jwt_sample.py
132 lines (112 loc) · 4.19 KB
/
jwt_sample.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import datetime
from functools import wraps
from flask import Flask, request, jsonify, make_response, Response
import jwt
from werkzeug.security import check_password_hash, generate_password_hash
"""
References
- https://datatracker.ietf.org/doc/html/rfc7519
- https://www.bacancytechnology.com/blog/flask-jwt-authentication
- https://blog.angular-university.io/angular-jwt/
"""
app = Flask(__name__)
"""
This is the secret key used to sign the JWT.
NOTE: This shouldn't be hardcoded in the program, instead placed in a configuration file outside the code with
restrictive file permissions.
"""
app.config['SECRET_KEY'] = '004f2af45d3a4e161a7dd2d17fdae47f'
def is_valid_user_credentials(username: str, password: str) -> bool:
"""
Checks if the provided user credentials are valid.
:param username: Username.
:param password: Plaintext password.
:return: True if the credentials are valid. Else False.
"""
# NOTE: The username and password hashes are usually verified from a database.
expected_username = "testuser"
expected_password_hash = generate_password_hash("dedewdewdew")
if username != expected_username:
return False
elif not check_password_hash(expected_password_hash, password):
return False
return True
@app.route('/login', methods=['POST'])
def login_user() -> Response:
"""
Handler for the user authentication.
:return: If successful, returns a 200 status code with the JWT. Else, returns the appropriate error status code.
"""
auth = request.authorization
if not auth or not auth.username or not auth.password:
return make_response(
'could not verify',
401,
{'Authentication': 'login required"'}
)
if is_valid_user_credentials(auth.username, auth.password):
token = jwt.encode(
{
'public_id': auth.username,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)
},
app.config['SECRET_KEY'],
"HS256"
)
"""
An encoded JWT will look like this:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJwdWJsaWNfaWQiOiJ0ZXN0dXNlciIsImV4cCI6MTYzMjgzNzA5NH0
.WYim4hf84SsQ5Eitc3TXQAc3PO7TbYtbDV2E3SDBxsA
This is the base64 representation of:
{"typ":"JWT","alg":"HS256"}
{"public_id":"testuser","exp":1632837094}
b)8J9+\5;bWa7H0q
Both the base64 and plaintext representation of the JWT has 3 parts.
- Header aka JSON Object Signing and Encryption (JOSE) header : Contains metadata on JWT.
Eg: Type of signature etc
- Payload : The actual user and session information.
- Signature : A signature of the Header and Payload only verifiable by the server that has the private key
that signed it.
"""
return jsonify({'token': token})
return make_response(
'could not verify',
401,
{'Authentication': '"login required"'}
)
def token_required(f):
"""
Decorator to handle JWT verification.
:param f: Handler function to decorate.
:return: The decorator.
"""
@wraps(f)
def decorator(*args, **kwargs):
token = None
if 'x-access-tokens' in request.headers:
token = request.headers['x-access-tokens']
if not token:
return jsonify({'message': 'a valid token is missing'})
try:
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
print(type(data))
print(data.keys())
current_user = data["public_id"]
except Exception as e:
print(e)
return jsonify({'message': 'token is invalid'})
return f(current_user, *args, **kwargs)
return decorator
@app.route('/')
@token_required
def hello(user: str = "Unknown"):
"""
A sample handler to demonstrate JWT validation.
:param user: Input username.
:return: A user greeting.
"""
return f"Hello, {user}!"
if __name__ == "__main__":
# Run the web server so that it can be accessed by any host on the network via port 5001.
app.run(host="0.0.0.0", port=5001)