Skip to content

Commit 7cecdb3

Browse files
author
Olivier Gintrand
committed
fix(oauth): handle missing expires_in in OAuth token response
Signed-off-by: Olivier Gintrand <olivier.gintrand@forterro.com>
1 parent a02a04b commit 7cecdb3

4 files changed

Lines changed: 26 additions & 12 deletions

File tree

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2136,6 +2136,11 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
21362136
# TOOL_CONCURRENT_LIMIT=10
21372137
# GATEWAY_TOOL_NAME_SEPARATOR=-
21382138

2139+
# Maximum length of response text returned for non-JSON REST API responses
2140+
# Longer responses are truncated to prevent exposing excessive sensitive data
2141+
# Default: 5000 characters, Range: 1000-100000
2142+
# REST_RESPONSE_TEXT_MAX_LENGTH=5000
2143+
21392144
# Prompt Configuration
21402145
# PROMPT_CACHE_SIZE=100
21412146
# MAX_PROMPT_SIZE=102400

.secrets.baseline

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "^.secrets.baseline|package-lock.json|Cargo.lock|scripts/sign_image.sh|scripts/zap|sonar-project.properties|uv.lock|go.sum|mcpgateway/sri_hashes.json|^.secrets.baseline$",
44
"lines": null
55
},
6-
"generated_at": "2026-04-14T13:09:46Z",
6+
"generated_at": "2026-04-14T14:08:10Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -4830,7 +4830,7 @@
48304830
"hashed_secret": "ff37a98a9963d347e9749a5c1b3936a4a245a6ff",
48314831
"is_secret": false,
48324832
"is_verified": false,
4833-
"line_number": 2228,
4833+
"line_number": 2236,
48344834
"type": "Secret Keyword",
48354835
"verified_result": null
48364836
}
@@ -8624,39 +8624,39 @@
86248624
"hashed_secret": "ee977806d7286510da8b9a7492ba58e2484c0ecc",
86258625
"is_secret": false,
86268626
"is_verified": false,
8627-
"line_number": 6376,
8627+
"line_number": 6907,
86288628
"type": "Secret Keyword",
86298629
"verified_result": null
86308630
},
86318631
{
86328632
"hashed_secret": "f2e7745f43b0ef0e2c2faf61d6c6a28be2965750",
86338633
"is_secret": false,
86348634
"is_verified": false,
8635-
"line_number": 6868,
8635+
"line_number": 7399,
86368636
"type": "Secret Keyword",
86378637
"verified_result": null
86388638
},
86398639
{
86408640
"hashed_secret": "4a249743d4d2241bd2ae085b4fe654d089488295",
86418641
"is_secret": false,
86428642
"is_verified": false,
8643-
"line_number": 8215,
8643+
"line_number": 8746,
86448644
"type": "Secret Keyword",
86458645
"verified_result": null
86468646
},
86478647
{
86488648
"hashed_secret": "0c8d051d3c7eada5d31b53d9936fce6bcc232ae2",
86498649
"is_secret": false,
86508650
"is_verified": false,
8651-
"line_number": 8357,
8651+
"line_number": 8888,
86528652
"type": "Secret Keyword",
86538653
"verified_result": null
86548654
},
86558655
{
86568656
"hashed_secret": "f2b14f68eb995facb3a1c35287b778d5bd785511",
86578657
"is_secret": false,
86588658
"is_verified": false,
8659-
"line_number": 8733,
8659+
"line_number": 9264,
86608660
"type": "Secret Keyword",
86618661
"verified_result": null
86628662
}

mcpgateway/services/oauth_manager.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,13 +573,18 @@ async def complete_authorization_code_flow(self, gateway_id: str, code: str, sta
573573

574574
# Store tokens if storage service is available
575575
if self.token_storage:
576+
# Use provider's expires_in if present; None means the provider
577+
# does not specify token expiration (e.g. GitHub OAuth Apps).
578+
raw_expires = token_response.get("expires_in")
579+
expires_in = int(raw_expires) if raw_expires is not None else None
580+
576581
token_record = await self.token_storage.store_tokens(
577582
gateway_id=gateway_id,
578583
user_id=user_id,
579584
app_user_email=app_user_email, # User from state
580585
access_token=token_response["access_token"],
581586
refresh_token=token_response.get("refresh_token"),
582-
expires_in=token_response.get("expires_in", self.settings.oauth_default_timeout),
587+
expires_in=expires_in,
583588
scopes=token_response.get("scope", "").split(),
584589
)
585590

mcpgateway/services/token_storage_service.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(self, db: Session):
7474
logger.warning("OAuth encryption not available, using plain text storage")
7575
self.encryption = None
7676

77-
async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str, access_token: str, refresh_token: Optional[str], expires_in: int, scopes: List[str]) -> OAuthToken:
77+
async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str, access_token: str, refresh_token: Optional[str], expires_in: Optional[int], scopes: List[str]) -> OAuthToken:
7878
"""Store OAuth tokens for a gateway-user combination.
7979
8080
Args:
@@ -83,7 +83,7 @@ async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str,
8383
app_user_email: ContextForge user email (required)
8484
access_token: Access token from OAuth provider
8585
refresh_token: Refresh token from OAuth provider (optional)
86-
expires_in: Token expiration time in seconds
86+
expires_in: Token expiration time in seconds, or None if the provider does not specify expiration
8787
scopes: List of OAuth scopes granted
8888
8989
Returns:
@@ -102,8 +102,12 @@ async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str,
102102
if refresh_token:
103103
encrypted_refresh = await self.encryption.encrypt_secret_async(refresh_token)
104104

105-
# Calculate expiration
106-
expires_at = datetime.now(timezone.utc) + timedelta(seconds=int(expires_in))
105+
# Calculate expiration (None if provider does not specify expires_in)
106+
if expires_in is not None:
107+
expires_at = datetime.now(timezone.utc) + timedelta(seconds=int(expires_in))
108+
else:
109+
logger.info(f"No expires_in from OAuth provider for gateway {SecurityValidator.sanitize_log_message(gateway_id)} — token will not auto-expire")
110+
expires_at = None
107111
# Create or update token record - now scoped by app_user_email
108112
token_record = self.db.execute(select(OAuthToken).where(OAuthToken.gateway_id == gateway_id, OAuthToken.app_user_email == app_user_email)).scalar_one_or_none()
109113

0 commit comments

Comments
 (0)