Skip to content

Commit c5614d9

Browse files
cosminachoclaude
andcommitted
fix: accept opaque UiPath reference tokens (rt_) as access token
UiPath Coded Agents can authenticate with reference tokens (rt_...), which are opaque handles rather than JWTs. PlatformSettings previously rejected them with "Invalid access token: expected JWT with at least 2 dot-separated parts" because validation assumed every token is a JWT. Add is_reference_token() helper; is_token_expired() now returns False for reference tokens (they cannot be introspected) and the settings validator skips client_id extraction for them. Bumps core and langchain to 1.13.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3c8ebcc commit c5614d9

8 files changed

Lines changed: 66 additions & 8 deletions

File tree

packages/uipath_langchain_client/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to `uipath_langchain_client` will be documented in this file.
44

5+
## [1.13.1] - 2026-06-09
6+
7+
### Fixed
8+
- Picks up the core `uipath-llm-client` 1.13.1 fix allowing opaque UiPath reference tokens (`rt_...`) as `UIPATH_ACCESS_TOKEN`, so LangChain clients built on `PlatformSettings` no longer fail validation with "Invalid access token: expected JWT with at least 2 dot-separated parts".
9+
510
## [1.13.0] - 2026-05-27
611

712
### Changed

packages/uipath_langchain_client/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
88
"langchain>=1.2.15,<2.0.0",
9-
"uipath-llm-client>=1.13.0,<2.0.0",
9+
"uipath-llm-client>=1.13.1,<2.0.0",
1010
]
1111

1212
[project.optional-dependencies]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LangChain Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
3-
__version__ = "1.13.0"
3+
__version__ = "1.13.1"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LLM Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services."
3-
__version__ = "1.13.0"
3+
__version__ = "1.13.1"

src/uipath/llm_client/settings/platform/settings.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030

3131
from uipath.llm_client.settings.base import UiPathAPIConfig, UiPathBaseSettings
3232
from uipath.llm_client.settings.constants import ApiType, RoutingMode
33-
from uipath.llm_client.settings.platform.utils import is_token_expired, parse_access_token
33+
from uipath.llm_client.settings.platform.utils import (
34+
is_reference_token,
35+
is_token_expired,
36+
parse_access_token,
37+
)
3438

3539

3640
class PlatformBaseSettings(UiPathBaseSettings):
@@ -81,8 +85,11 @@ def validate_environment(self) -> Self:
8185
"Access token is expired. Try running `uipath auth` to refresh the token."
8286
)
8387

84-
parsed_token_data = parse_access_token(access_token)
85-
self.client_id = parsed_token_data.get("client_id")
88+
# Reference tokens (rt_...) are opaque and carry no client-readable
89+
# claims, so there is nothing to extract.
90+
if not is_reference_token(access_token):
91+
parsed_token_data = parse_access_token(access_token)
92+
self.client_id = parsed_token_data.get("client_id")
8693
return self
8794

8895
@staticmethod

src/uipath/llm_client/settings/platform/utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
import time
44
from typing import Any
55

6+
# UiPath reference tokens (e.g. ``rt_abc...``) are opaque handles, not JWTs.
7+
# They cannot be split, decoded, or introspected client-side.
8+
REFERENCE_TOKEN_PREFIX = "rt_"
9+
10+
11+
def is_reference_token(access_token: str) -> bool:
12+
"""Return True if the token is an opaque UiPath reference token (``rt_...``).
13+
14+
Reference tokens are not JWTs and carry no client-readable payload, so
15+
expiry checks and claim extraction must be skipped for them.
16+
"""
17+
return access_token.startswith(REFERENCE_TOKEN_PREFIX)
18+
619

720
def parse_access_token(access_token: str) -> dict[str, Any]:
821
"""Parse a JWT access token and return the payload as a dict.
@@ -33,8 +46,11 @@ def is_token_expired(token: str) -> bool:
3346
token: A JWT token string.
3447
3548
Returns:
36-
True if the token is expired, False if it is still valid or has no ``exp`` claim.
49+
True if the token is expired, False if it is still valid, has no ``exp``
50+
claim, or is an opaque reference token whose expiry cannot be inspected.
3751
"""
52+
if is_reference_token(token):
53+
return False
3854
token_data = parse_access_token(token)
3955
exp = token_data.get("exp")
4056
if exp is None:

tests/core/features/settings/test_platform.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,19 @@ def test_validation_fails_on_expired_token(self):
250250
with pytest.raises(ValueError, match="Access token is expired"):
251251
PlatformSettings()
252252

253+
def test_reference_token_is_accepted(self):
254+
"""Opaque reference tokens (rt_...) are accepted without JWT parsing."""
255+
env = {
256+
"UIPATH_ACCESS_TOKEN": "rt_abc123",
257+
"UIPATH_URL": "https://cloud.uipath.com/org/tenant",
258+
"UIPATH_TENANT_ID": "test-tenant-id",
259+
"UIPATH_ORGANIZATION_ID": "test-org-id",
260+
}
261+
with patch.dict(os.environ, env, clear=True):
262+
settings = PlatformSettings()
263+
assert settings.access_token.get_secret_value() == "rt_abc123"
264+
assert settings.client_id is None
265+
253266
def test_validate_byo_model_is_noop(self, platform_env_vars, mock_platform_auth):
254267
"""Test validate_byo_model does nothing (no-op)."""
255268
with patch.dict(os.environ, platform_env_vars, clear=True):

tests/core/features/test_platform_utils.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
import pytest
88

9-
from uipath.llm_client.settings.platform.utils import is_token_expired, parse_access_token
9+
from uipath.llm_client.settings.platform.utils import (
10+
is_reference_token,
11+
is_token_expired,
12+
parse_access_token,
13+
)
1014

1115

1216
class TestParseAccessToken:
@@ -69,3 +73,16 @@ def test_missing_exp_claim(self):
6973
token = f"header.{encoded_payload}.signature"
7074

7175
assert is_token_expired(token) is False
76+
77+
def test_reference_token_not_expired(self):
78+
# Opaque reference tokens cannot be introspected, so they are never
79+
# treated as expired (and must not raise during parsing).
80+
assert is_token_expired("rt_abc123") is False
81+
82+
83+
class TestIsReferenceToken:
84+
def test_reference_token(self):
85+
assert is_reference_token("rt_abc123") is True
86+
87+
def test_jwt_is_not_reference_token(self):
88+
assert is_reference_token("header.payload.signature") is False

0 commit comments

Comments
 (0)