-
Notifications
You must be signed in to change notification settings - Fork 0
Change JWT encryption from HMAC to RSA with rotating keys #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
2614df4
Enhance Docker and JWT handling: update Dockerfile to install OpenSSL…
c8bc60e
Implement JWT key rotation: add endpoint to rotate keys, update key m…
968da27
Refactor JWT key handling: remove hardcoded key loading, utilize dyna…
01eff90
Add timestamp logging for key creation in JWT rotation
702725a
Change route to be more RESTful
f2834ee
Remove JWT_SECRET_KEY from config and add script for cleaning up expi…
6bc3104
Refactor JWT tests to use public key for encoding/decoding and enhanc…
ffb491a
Remove redundant sample_person_id fixture from test_refresh.py
ab6d222
Merge branch 'main' into security/use_rsa_for_jwt
ae6c76a
Refactor key cleanup script to use dynamic expiry days from JWT confi…
5c0e461
Refactor JWT handling by moving related functions and classes to a ne…
7b392a5
Refactor JWT key handling by consolidating functions into common modu…
21602d3
Refactor JWT test suite by removing obsolete test files and consolida…
b1f94b3
Clarify entrypoint script comment to specify starting the API
e4078fd
Optimize payload generation
Vianpyro bc7d9d0
Merge branch 'main' into security/use_rsa_for_jwt
68a2081
Add key management functionality and refactor key paths
8b05ed4
Refactor Dockerfile to remove entrypoint script and directly run key …
9d4a087
Add OpenSSL installation steps for Linux, macOS, and Windows in Pytes…
3ad2c9c
Fix variable name inconsistency for key directory in jwtoken and keys…
2eb3e51
Refactor Dockerfile to use entrypoint script for key rotation and app…
926a4cc
Reorganize Dockerfile to copy entrypoint script before exposing Flask…
8c15694
Remove entrypoint script copy command from Dockerfile; streamline app…
7ebd0e5
Update ENTRYPOINT in Dockerfile to use entrypoint.sh for improved pro…
89ba7cd
Update ENTRYPOINT in Dockerfile to use entrypoint.sh for improved pro…
7e9fc5f
Refactor app.py to use ACTIVE_KID_FILE constant for key existence che…
cd26564
Remove OpenSSL installation step for macOS in Pytest CI workflow; cla…
5c18cc5
Refactor datetime usage in token generation and logging setup; improv…
c28cf21
Remove entrypoint.sh and update ENTRYPOINT in Dockerfile to directly …
b9f9624
Add curl to fix healthcheck and remove cached libraries to save up space
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,8 @@ | |
| logs/ | ||
| *.log | ||
|
|
||
| # Ignore all pem files | ||
| # Ignore JWT keys | ||
| keys/ | ||
| *.pem | ||
|
|
||
| # Upload folder | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #!/bin/sh | ||
|
|
||
| set -e # Exit if any command fails | ||
|
|
||
| KEY_DIR="/app/keys" | ||
| PRIVATE_KEY="$KEY_DIR/private_key.pem" | ||
| PUBLIC_KEY="$KEY_DIR/public_key.pem" | ||
|
|
||
| mkdir -p "$KEY_DIR" | ||
|
|
||
| if [ ! -f "$PRIVATE_KEY" ] || [ ! -f "$PUBLIC_KEY" ]; then | ||
| echo "🔐 Generating RSA key pair..." | ||
| openssl genpkey -algorithm RSA -out "$PRIVATE_KEY" -pkeyopt rsa_keygen_bits:2048 | ||
| openssl rsa -pubout -in "$PRIVATE_KEY" -out "$PUBLIC_KEY" | ||
| else | ||
| echo "✅ Keys already exist. Skipping generation." | ||
| fi | ||
|
|
||
| # Start the API | ||
| exec python3 app.py |
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| from functools import wraps | ||
|
|
||
| from flask import jsonify, request | ||
|
|
||
| from utility.jwtoken.common import extract_token_from_header | ||
|
|
||
| from .exceptions import TokenError | ||
| from .tokens import verify_token | ||
|
|
||
|
|
||
| def token_required(f): | ||
| """Decorator to protect routes by requiring a valid token.""" | ||
|
|
||
| @wraps(f) | ||
| def decorated(*args, **kwargs): | ||
| try: | ||
| token = extract_token_from_header() | ||
| decoded = verify_token(token, required_type="access") | ||
| request.person_id = decoded["person_id"] | ||
| return f(*args, **kwargs) | ||
| except TokenError as e: | ||
| return jsonify(message=e.message), e.status_code | ||
|
|
||
| return decorated |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| class TokenError(Exception): | ||
| """Custom exception for token-related errors.""" | ||
|
|
||
| def __init__(self, message, status_code): | ||
| super().__init__(message) | ||
| self.status_code = status_code | ||
| self.message = message |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| from datetime import datetime, timedelta, timezone | ||
|
|
||
| import jwt | ||
|
|
||
| from utility.jwtoken.common import get_active_kid, load_private_key, load_public_key | ||
|
|
||
| from .exceptions import TokenError | ||
|
|
||
| JWT_ACCESS_TOKEN_EXPIRY = timedelta(hours=1) | ||
| JWT_REFRESH_TOKEN_EXPIRY = timedelta(days=30) | ||
|
|
||
|
|
||
| def generate_access_token(person_id: int) -> str: | ||
| kid = get_active_kid() | ||
| private_key = load_private_key(kid) | ||
|
|
||
| payload = { | ||
| "person_id": person_id, | ||
| "exp": datetime.now(timezone.utc) + JWT_ACCESS_TOKEN_EXPIRY, # Expiration | ||
| "iat": datetime.now(timezone.utc), # Issued at | ||
| "token_type": "access", | ||
| } | ||
| headers = {"kid": kid} | ||
| return jwt.encode(payload, private_key, algorithm="RS256", headers=headers) | ||
|
|
||
|
|
||
| def generate_refresh_token(person_id: int) -> str: | ||
| """Generate a long-lived refresh token for a user.""" | ||
| kid = get_active_kid() | ||
| private_key = load_private_key(kid) | ||
|
|
||
| payload = { | ||
| "person_id": person_id, | ||
| "exp": datetime.now(timezone.utc) + JWT_REFRESH_TOKEN_EXPIRY, # Expiration | ||
| "iat": datetime.now(timezone.utc), # Issued at | ||
| "token_type": "refresh", | ||
| } | ||
| headers = {"kid": kid} | ||
|
|
||
| return jwt.encode(payload, private_key, algorithm="RS256", headers=headers) | ||
|
|
||
|
|
||
| def verify_token(token: str, required_type: str) -> dict: | ||
| """Verify and decode a JWT token.""" | ||
| try: | ||
| unverified_header = jwt.get_unverified_header(token) | ||
| kid = unverified_header.get("kid") | ||
| if not kid: | ||
| raise TokenError("KID missing in token header", 401) | ||
|
|
||
| public_key = load_public_key(kid) | ||
|
|
||
| decoded = jwt.decode(token, public_key, algorithms=["RS256"]) | ||
| if decoded.get("token_type") != required_type: | ||
| raise jwt.InvalidTokenError("Invalid token type") | ||
| return decoded | ||
|
|
||
| except jwt.ExpiredSignatureError: | ||
| raise TokenError("Token has expired", 401) | ||
| except jwt.InvalidTokenError: | ||
| raise TokenError("Invalid token", 401) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import pytest | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def sample_person_id() -> int: | ||
| """Provide a sample person ID for testing""" | ||
| return 12345 |
This file was deleted.
Oops, something went wrong.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 2 additions & 1 deletion
3
...est_jwt/test_extract_token_from_header.py → ...jwtoken/test_extract_token_from_header.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.