Skip to content

Commit 1c669cd

Browse files
add user jwt auth, providing access and refresh tokens
1 parent a44c7cf commit 1c669cd

File tree

4 files changed

+142
-2
lines changed

4 files changed

+142
-2
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
SQLALCHEMY_DATABASE_URI=your_database_url
2+
JWT_SECRET_KEY=your_super-secret-key

app/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
2+
from datetime import timedelta
23

3-
from flask import Flask
4+
from flask import Flask, jsonify
5+
from flask_jwt_extended import JWTManager
46
from flask_migrate import Migrate
57
from flask_sqlalchemy import SQLAlchemy
68
from dotenv import load_dotenv
@@ -14,6 +16,10 @@
1416
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv("SQLALCHEMY_DATABASE_URI")
1517
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
1618

19+
app.config["JWT_SECRET_KEY"] = os.getenv("JWT_SECRET_KEY")
20+
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=3)
21+
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=3)
22+
1723
# PostgreSQL-compatible naming convention (to follow the naming convention already used in the DB)
1824
# https://stackoverflow.com/questions/4107915/postgresql-default-constraint-names
1925
naming_convention = {
@@ -26,6 +32,7 @@
2632
metadata = MetaData(naming_convention=naming_convention)
2733
db = SQLAlchemy(app, metadata=metadata)
2834
migrate = Migrate(app, db)
35+
jwt = JWTManager(app)
2936

3037
from app import routes
3138

app/routes.py

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,138 @@
11
from flask import request, abort, jsonify
2+
from flask_jwt_extended import create_access_token, create_refresh_token, get_jwt_identity, jwt_required
3+
from sqlalchemy.exc import IntegrityError
4+
from email_validator import EmailNotValidError
25

36
from app import app, db
4-
from app.models import Category, Subcategory, Product, category_subcategory, subcategory_product
7+
from app.models import Category, Subcategory, Product, User, category_subcategory, subcategory_product
8+
9+
10+
@app.route('/auth/register', methods=['POST'])
11+
def register():
12+
"""
13+
Register a new user.
14+
---
15+
tags:
16+
- User
17+
description: Register a new user.
18+
requestBody:
19+
required: true
20+
description: email - Email id <br> password - Password
21+
content:
22+
application/json:
23+
schema:
24+
type: object
25+
required:
26+
- email
27+
- password
28+
properties:
29+
email:
30+
type: string
31+
password:
32+
type: string
33+
responses:
34+
201:
35+
description: User registered successfully.
36+
400:
37+
description: Invalid input.
38+
409:
39+
description: Email already exists.
40+
500:
41+
description: Internal Server Error.
42+
"""
43+
if not request.json:
44+
abort(400)
45+
46+
try:
47+
email = request.json.get('email')
48+
password = request.json.get('password')
49+
if not email or not password:
50+
abort(400)
51+
52+
user = User()
53+
user.set_email(email)
54+
user.set_password(password)
55+
db.session.add(user)
56+
db.session.commit()
57+
return { "message": "Registered!" }, 201
58+
except IntegrityError:
59+
return jsonify({'error': 'Email already exists'}), 409
60+
except EmailNotValidError as e:
61+
return jsonify(code='invalid_email_format', error=str(e)), 400
62+
63+
64+
@app.route('/auth/login', methods=['POST'])
65+
def login():
66+
"""
67+
Login a user.
68+
---
69+
tags:
70+
- User
71+
description: Login a user.
72+
requestBody:
73+
required: true
74+
description: email - Email id <br> password - Password
75+
content:
76+
application/json:
77+
schema:
78+
type: object
79+
required:
80+
- email
81+
- password
82+
properties:
83+
email:
84+
type: string
85+
password:
86+
type: string
87+
responses:
88+
200:
89+
description: User logged in successfully.
90+
400:
91+
description: Invalid input.
92+
401:
93+
description: Invalid username or password.
94+
500:
95+
description: Internal Server Error.
96+
"""
97+
if not request.json:
98+
abort(400)
99+
100+
email = request.json.get('email')
101+
password = request.json.get('password')
102+
if not email or not password:
103+
abort(400)
104+
105+
user = User.get(email=email)
106+
if not user or not user.check_password(password):
107+
return jsonify(error='Invalid username or password. Check again or register.'), 401
108+
109+
access_token = create_access_token(identity=str(user.id), fresh=True)
110+
refresh_token = create_refresh_token(identity=str(user.id))
111+
return jsonify(access_token=access_token, refresh_token=refresh_token), 200
112+
113+
114+
@app.route('/auth/refresh', methods=['POST'])
115+
@jwt_required(refresh=True)
116+
def refresh():
117+
"""
118+
Get new access token using your refresh token
119+
---
120+
tags:
121+
- User
122+
description: Get new access token using your refresh token.
123+
security:
124+
- refresh_token: []
125+
responses:
126+
200:
127+
description: New access token.
128+
401:
129+
description: Token expired, missing or invalid.
130+
500:
131+
description: Internal Server Error.
132+
"""
133+
identity = get_jwt_identity()
134+
access_token = create_access_token(identity=identity, fresh=False)
135+
return jsonify(access_token=access_token), 200
5136

6137

7138
@app.route('/category/create', methods=['POST'])

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ flasgger==0.9.7.1
77
Flask-Migrate==4.1.0
88
email_validator==1.3.1
99
email-normalize==0.2.1
10+
Flask-JWT-Extended==4.7.1

0 commit comments

Comments
 (0)