Skip to content

Commit 8a13303

Browse files
committed
added rsa token and adjusted logic accordingly
1 parent e32208f commit 8a13303

File tree

6 files changed

+133
-196
lines changed

6 files changed

+133
-196
lines changed

.env.example

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ RDS_BACKEND_BASE_URL='http://localhost:3000'
77
RDS_PUBLIC_KEY="public-key-here"
88
GOOGLE_OAUTH_CLIENT_ID="google-client-id"
99
GOOGLE_OAUTH_CLIENT_SECRET="client-secret"
10-
GOOGLE_OAUTH_REDIRECT_URI="environment-url/auth/google/callback"
11-
GOOGLE_JWT_SECRET_KEY=generate-secret-key
10+
# Google JWT RSA Keys
11+
GOOGLE_JWT_PRIVATE_KEY="generate keys and paste here"
12+
GOOGLE_JWT_PUBLIC_KEY="generate keys and paste here"
1213

13-
GOOGLE_JWT_SECRET_KEY="7i=aje)abyu!8m3jc9&vlmo@e-o__65_jw_=g^ktw7@+saa1z("
14-
GOOGLE_OAUTH_REDIRECT_URI="http://localhost:8000/v1/auth/google/callback"
14+
# use if required
15+
# GOOGLE_JWT_ACCESS_LIFETIME="20"
16+
# GOOGLE_JWT_REFRESH_LIFETIME="30"

todo/middlewares/jwt_auth.py

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@
33
from django.http import JsonResponse
44

55
from todo.utils.jwt_utils import verify_jwt_token
6-
from todo.utils.google_jwt_utils import validate_google_access_token
6+
from todo.utils.google_jwt_utils import (
7+
validate_google_access_token,
8+
validate_google_refresh_token,
9+
generate_google_access_token,
10+
)
711
from todo.exceptions.auth_exceptions import TokenMissingError, TokenExpiredError, TokenInvalidError
8-
from todo.exceptions.google_auth_exceptions import GoogleTokenExpiredError, GoogleTokenInvalidError
12+
from todo.exceptions.google_auth_exceptions import (
13+
GoogleTokenExpiredError,
14+
GoogleTokenInvalidError,
15+
GoogleRefreshTokenExpiredError,
16+
)
917
from todo.constants.messages import AuthErrorMessages, ApiErrors
1018
from todo.dto.responses.error_response import ApiErrorResponse, ApiErrorDetail
1119

@@ -25,7 +33,8 @@ def __call__(self, request):
2533
auth_success = self._try_authentication(request)
2634

2735
if auth_success:
28-
return self.get_response(request)
36+
response = self.get_response(request)
37+
return self._process_response(request, response)
2938
else:
3039
error_response = ApiErrorResponse(
3140
statusCode=status.HTTP_401_UNAUTHORIZED,
@@ -38,7 +47,8 @@ def __call__(self, request):
3847
],
3948
)
4049
return JsonResponse(
41-
data=error_response.model_dump(mode="json", exclude_none=True), status=status.HTTP_401_UNAUTHORIZED
50+
data=error_response.model_dump(mode="json", exclude_none=True),
51+
status=status.HTTP_401_UNAUTHORIZED
4252
)
4353

4454
except (TokenMissingError, TokenExpiredError, TokenInvalidError) as e:
@@ -57,7 +67,8 @@ def __call__(self, request):
5767
],
5868
)
5969
return JsonResponse(
60-
data=error_response.model_dump(mode="json", exclude_none=True), status=status.HTTP_401_UNAUTHORIZED
70+
data=error_response.model_dump(mode="json", exclude_none=True),
71+
status=status.HTTP_401_UNAUTHORIZED
6172
)
6273

6374
def _try_authentication(self, request) -> bool:
@@ -73,25 +84,61 @@ def _try_google_auth(self, request) -> bool:
7384
try:
7485
google_token = request.COOKIES.get("ext-access")
7586

76-
if not google_token:
77-
return False
78-
79-
payload = validate_google_access_token(google_token)
87+
if google_token:
88+
try:
89+
payload = validate_google_access_token(google_token)
90+
self._set_google_user_data(request, payload)
91+
return True
92+
except (GoogleTokenExpiredError, GoogleTokenInvalidError):
93+
pass
8094

81-
request.auth_type = "google"
82-
request.user_id = payload["user_id"]
83-
request.google_id = payload["google_id"]
84-
request.user_email = payload["email"]
85-
request.user_name = payload["name"]
86-
request.user_role = "external_user"
87-
88-
return True
95+
return self._try_google_refresh(request)
8996

9097
except (GoogleTokenExpiredError, GoogleTokenInvalidError) as e:
9198
raise e
9299
except Exception:
93100
return False
94101

102+
def _try_google_refresh(self, request) -> bool:
103+
"""Try to refresh Google access token"""
104+
try:
105+
refresh_token = request.COOKIES.get("ext-refresh")
106+
107+
if not refresh_token:
108+
return False
109+
110+
payload = validate_google_refresh_token(refresh_token)
111+
112+
user_data = {
113+
"user_id": payload["user_id"],
114+
"google_id": payload["google_id"],
115+
"email": payload["email"],
116+
"name": payload.get("name", ""),
117+
}
118+
119+
new_access_token = generate_google_access_token(user_data)
120+
121+
self._set_google_user_data(request, payload)
122+
123+
request._new_access_token = new_access_token
124+
request._access_token_expires = settings.GOOGLE_JWT["ACCESS_TOKEN_LIFETIME"]
125+
126+
return True
127+
128+
except (GoogleRefreshTokenExpiredError, GoogleTokenInvalidError):
129+
return False
130+
except Exception:
131+
return False
132+
133+
def _set_google_user_data(self, request, payload):
134+
"""Set Google user data on request"""
135+
request.auth_type = "google"
136+
request.user_id = payload["user_id"]
137+
request.google_id = payload["google_id"]
138+
request.user_email = payload["email"]
139+
request.user_name = payload.get("name", "")
140+
request.user_role = "external_user"
141+
95142
def _try_rds_auth(self, request) -> bool:
96143
try:
97144
rds_token = request.COOKIES.get(self.rds_cookie_name)
@@ -112,6 +159,28 @@ def _try_rds_auth(self, request) -> bool:
112159
except Exception:
113160
return False
114161

162+
def _process_response(self, request, response):
163+
"""Process response and set new cookies if Google token was refreshed"""
164+
if hasattr(request, '_new_access_token'):
165+
config = self._get_cookie_config()
166+
response.set_cookie(
167+
"ext-access",
168+
request._new_access_token,
169+
max_age=request._access_token_expires,
170+
**config
171+
)
172+
return response
173+
174+
def _get_cookie_config(self):
175+
"""Get Google cookie configuration"""
176+
return {
177+
"path": "/",
178+
"domain": settings.GOOGLE_COOKIE_SETTINGS.get("COOKIE_DOMAIN"),
179+
"secure": settings.GOOGLE_COOKIE_SETTINGS.get("COOKIE_SECURE", False),
180+
"httponly": True,
181+
"samesite": settings.GOOGLE_COOKIE_SETTINGS.get("COOKIE_SAMESITE", "Lax"),
182+
}
183+
115184
def _is_public_path(self, path: str) -> bool:
116185
return any(path.startswith(public_path) for public_path in settings.PUBLIC_PATHS)
117186

@@ -122,7 +191,8 @@ def _handle_rds_auth_error(self, exception):
122191
errors=[ApiErrorDetail(title=ApiErrors.AUTHENTICATION_FAILED, detail=str(exception))],
123192
)
124193
return JsonResponse(
125-
data=error_response.model_dump(mode="json", exclude_none=True), status=status.HTTP_401_UNAUTHORIZED
194+
data=error_response.model_dump(mode="json", exclude_none=True),
195+
status=status.HTTP_401_UNAUTHORIZED
126196
)
127197

128198
def _handle_google_auth_error(self, exception):
@@ -132,7 +202,8 @@ def _handle_google_auth_error(self, exception):
132202
errors=[ApiErrorDetail(title=ApiErrors.AUTHENTICATION_FAILED, detail=str(exception))],
133203
)
134204
return JsonResponse(
135-
data=error_response.model_dump(mode="json", exclude_none=True), status=status.HTTP_401_UNAUTHORIZED
205+
data=error_response.model_dump(mode="json", exclude_none=True),
206+
status=status.HTTP_401_UNAUTHORIZED
136207
)
137208

138209

@@ -169,4 +240,4 @@ def get_current_user_info(request) -> dict:
169240
}
170241
)
171242

172-
return user_info
243+
return user_info

todo/urls.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from todo.views.auth import (
66
GoogleLoginView,
77
GoogleCallbackView,
8-
GoogleRefreshView,
98
GoogleLogoutView,
109
)
1110

@@ -16,6 +15,5 @@
1615
path("labels", LabelListView.as_view(), name="labels"),
1716
path("auth/google/login/", GoogleLoginView.as_view(), name="google_login"),
1817
path("auth/google/callback/", GoogleCallbackView.as_view(), name="google_callback"),
19-
path("auth/google/refresh/", GoogleRefreshView.as_view(), name="google_refresh"),
2018
path("auth/google/logout/", GoogleLogoutView.as_view(), name="google_logout"),
2119
]

todo/utils/google_jwt_utils.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
GoogleRefreshTokenExpiredError,
99
)
1010

11-
from todo.constants.messages import AuthErrorMessages, ApiErrors
11+
from todo.constants.messages import AuthErrorMessages
1212

1313

1414
def generate_google_access_token(user_data: dict) -> str:
@@ -28,14 +28,11 @@ def generate_google_access_token(user_data: dict) -> str:
2828
"token_type": "access",
2929
}
3030

31-
token = jwt.encode(
32-
payload=payload, key=settings.GOOGLE_JWT["SECRET_KEY"], algorithm=settings.GOOGLE_JWT["ALGORITHM"]
33-
)
34-
31+
token = jwt.encode(payload=payload, key=settings.GOOGLE_JWT["PRIVATE_KEY"], algorithm=settings.GOOGLE_JWT["ALGORITHM"])
3532
return token
3633

37-
except Exception:
38-
raise GoogleTokenInvalidError(ApiErrors.GOOGLE_API_ERROR)
34+
except Exception as e:
35+
raise GoogleTokenInvalidError(f"Token generation failed: {str(e)}")
3936

4037

4138
def generate_google_refresh_token(user_data: dict) -> str:
@@ -53,22 +50,17 @@ def generate_google_refresh_token(user_data: dict) -> str:
5350
"email": user_data["email"],
5451
"token_type": "refresh",
5552
}
56-
57-
token = jwt.encode(
58-
payload=payload, key=settings.GOOGLE_JWT["SECRET_KEY"], algorithm=settings.GOOGLE_JWT["ALGORITHM"]
59-
)
53+
token = jwt.encode(payload=payload, key=settings.GOOGLE_JWT["PRIVATE_KEY"], algorithm=settings.GOOGLE_JWT["ALGORITHM"])
6054

6155
return token
6256

63-
except Exception:
64-
raise GoogleTokenInvalidError(ApiErrors.GOOGLE_API_ERROR)
57+
except Exception as e:
58+
raise GoogleTokenInvalidError(f"Refresh token generation failed: {str(e)}")
6559

6660

6761
def validate_google_access_token(token: str) -> dict:
6862
try:
69-
payload = jwt.decode(
70-
jwt=token, key=settings.GOOGLE_JWT["SECRET_KEY"], algorithms=[settings.GOOGLE_JWT["ALGORITHM"]]
71-
)
63+
payload = jwt.decode(jwt=token, key=settings.GOOGLE_JWT["PUBLIC_KEY"], algorithms=[settings.GOOGLE_JWT["ALGORITHM"]])
7264

7365
if payload.get("token_type") != "access":
7466
raise GoogleTokenInvalidError(AuthErrorMessages.GOOGLE_TOKEN_INVALID)
@@ -77,25 +69,26 @@ def validate_google_access_token(token: str) -> dict:
7769

7870
except jwt.ExpiredSignatureError:
7971
raise GoogleTokenExpiredError()
80-
except jwt.InvalidTokenError:
81-
raise GoogleTokenInvalidError(AuthErrorMessages.GOOGLE_TOKEN_INVALID)
72+
except jwt.InvalidTokenError as e:
73+
raise GoogleTokenInvalidError(f"Invalid token: {str(e)}")
74+
except Exception as e:
75+
raise GoogleTokenInvalidError(f"Token validation failed: {str(e)}")
8276

8377

8478
def validate_google_refresh_token(token: str) -> dict:
8579
try:
86-
payload = jwt.decode(
87-
jwt=token, key=settings.GOOGLE_JWT["SECRET_KEY"], algorithms=[settings.GOOGLE_JWT["ALGORITHM"]]
88-
)
89-
80+
payload = jwt.decode(jwt=token, key=settings.GOOGLE_JWT["PUBLIC_KEY"], algorithms=[settings.GOOGLE_JWT["ALGORITHM"]])
9081
if payload.get("token_type") != "refresh":
9182
raise GoogleTokenInvalidError(AuthErrorMessages.GOOGLE_TOKEN_INVALID)
9283

9384
return payload
9485

9586
except jwt.ExpiredSignatureError:
9687
raise GoogleRefreshTokenExpiredError()
97-
except jwt.InvalidTokenError:
98-
raise GoogleTokenInvalidError(AuthErrorMessages.GOOGLE_TOKEN_INVALID)
88+
except jwt.InvalidTokenError as e:
89+
raise GoogleTokenInvalidError(f"Invalid refresh token: {str(e)}")
90+
except Exception as e:
91+
raise GoogleTokenInvalidError(f"Refresh token validation failed: {str(e)}")
9992

10093

10194
def generate_google_token_pair(user_data: dict) -> dict:

0 commit comments

Comments
 (0)