This is a demo API application built using Flask, JWT (JSON Web Tokens), and SQLAlchemy with SQLite and Redis. The application follows best practices for user authentication and password management, including salting and hashing passwords. It implements key design patterns and ensures token-based security using JWT.
- API-based user authentication using JWT.
- Token-based access control for all user actions.
- Salting and hashing of passwords for enhanced security.
- Full CRUD operations on the User model (create, read, update, delete).
- Redis integration for token revocation and renewal management.
- Token expiry and refresh functionality.
- Implements Facade Pattern for handling authentication and business logic.
- Facade Pattern: The authentication logic is encapsulated in a facade (
AuthFacade
), making the API structure more modular and separating concerns. - Singleton Pattern: The
AuthService
and theUserService
classes are implemented as singleton to ensure only one instance handles authentication logic or user logic, respectively, reducing overhead.
- Python 3.x
- pip (Python package manager)
- Redis (For token management)
The application uses JSON Web Tokens (JWT) for secure user authentication. Each token has an expiration time for security purposes. Once a token expires, the user can renew it to continue accessing protected routes without having to re-authenticate. Here’s how token renewal is handled:
- Token Expiration: Each JWT token contains an
exp
field (expiration time) that defines how long the token is valid. Once this time is reached, the token becomes invalid. - Renewal Mechanism: When a token is close to expiration or has expired, the user can request a new token. The
AuthFacade
handles this through therenew_token
method, which verifies the current token and issues a new one with an extended expiration time.
-
Validate the Token: When a request is made with a token, the
jwt_required
decorator validates whether the token is still valid or has expired. -
Renew the Token: If the token is valid, the system issues a new JWT with a refreshed
exp
(expiration) value. The renewal process generates a new token using therenew_token
method from theAuthService
class. The new token includes:- The user’s ID (
user_id
) - A new expiration timestamp (
exp
) - A new token identifier (
jti
)
- The user’s ID (
-
Set Token in Redis: After generating the new token, the system stores it in Redis, associating it with the new expiration timestamp to ensure it’s only valid for the specified time.
- Short-Lived Tokens: Tokens are deliberately kept short-lived (e.g., 35 minutes) to limit the potential damage if a token is compromised. Users must request a new token if their current token expires.
- Revoking Tokens: If a token is revoked, it will be removed from Redis, and the user must re-authenticate to obtain a new token. This ensures that expired or revoked tokens cannot be reused.
-
POST /user: Create a new user.
- Request Body:
{ "username": "string", "password": "string" }
- Response:
{ "message": "User created successfully" }
- Request Body:
-
GET /user: Get the current user's information.
- Headers:
Authorization: Bearer <token>
- Response:
{ "user": { "id": "int", "username": "string" }, "token": "string" }
- Headers:
-
PUT /user: Update the current user's information.
- Headers:
Authorization: Bearer <token>
- Request Body:
{ "username": "string", "password": "string" }
- Response:
{ "message": "User updated successfully", "token": "string" }
- Headers:
-
DELETE /user: Delete the current user's account.
- Headers:
Authorization: Bearer <token>
- Response:
{ "message": "User deleted successfully" }
- Headers:
-
POST /authenticate_user: Authenticate a user and get a token.
- Request Body:
{ "username": "string", "password": "string" }
- Response:
{ "token": "string" }
- Request Body:
-
POST /validate_token: Validate a token.
- Request Body:
{ "token": "string" }
- Response:
{ "message":"Token is valid": "token":"sting" }
- Request Body:
-
POST /logout: Logout a user.
- Request Body:
{ "token": "string" }
- Response:
{ "message": "Logged out successfully" }
- Request Body:
The project is structured as follows:
├── main.py # Main application file
├── models
│ └── user.py # User model and database definitions
├── services
│ └── auth_service.py # Authentication logic and JWT handling
│ └── user_service.py # User CRUD operations
├── facades
│ └── auth_facade.py # Facade layer for login and validation
│ └── user_facade.py # Facade layer for user operations
├── tests
│ └── test_auth.py # Unit tests
├── README.md # Project README
└── .env (optional) # Environment variables (not committed- developmet default are located in auth_service.py)
The .env
file is used to store environment variables for the application. The following variables are used:
- 'JWT_KEY': The secret key used to sign JWT tokens.
- 'REDIS_HOST': The Redis server host.
- 'REDIS_PORT': The Redis server port.
- Salting: Before storing the user's password, the system generates a random salt (a sequence of random bytes) and concatenates it with the password. This helps protect against rainbow table attacks.
- Hashing: The salted password is hashed using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm with the SHA-256 hashing function. Token Management
- Redis: Tokens are stored and managed in Redis. When a token is created, its jti (JWT ID) is stored in Redis with an expiration time. If a token is revoked, the jti is removed from Redis.
- Token Expiry: Tokens expire after 35 minutes, but users can renew them within the application.
To run the tests, use the following command:
python -m unittest tests/test_auth.py
in the root directory, run the following command:
python main.py
- Python 3.10+
pip
(Python package installer)- Redis server
The application uses an SQLite database to store user information.
The database file (users.db
)
for creating the BD file a db_creator.py could be used to create the database file.
the command to create the database file is:
python db_creator.py
the database configuration is located in the main.py file:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
Feel free to fork the project and submit pull requests for improvements or bug fixes.
This project is open-source and available under the MIT License.