Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

AUTHORIZATION_HEADER_NAME = "authorization"
AUTHORIZATION_BEARER_PREFIX = "Bearer "
RAY_AUTHORIZATION_HEADER_NAME = "x-ray-authorization"

AUTHENTICATION_TOKEN_COOKIE_NAME = "ray-authentication-token"
AUTHENTICATION_TOKEN_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 # 30 days
12 changes: 10 additions & 2 deletions python/ray/_private/authentication/http_token_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,20 @@ async def token_auth_middleware(request, handler):
):
return await handler(request)

# Check Authorization header first (for API clients)
# Try to get authentication token from multiple sources (in priority order):
# 1. Standard "Authorization" header (for API clients, SDKs)
# 2. Fallback "X-Ray-Authorization" header (for proxies and KubeRay)
# 3. Cookie (for web dashboard sessions)

auth_header = request.headers.get(
authentication_constants.AUTHORIZATION_HEADER_NAME, ""
)

# If no Authorization header, check cookie (for web dashboard)
if not auth_header:
auth_header = request.headers.get(
authentication_constants.RAY_AUTHORIZATION_HEADER_NAME, ""
)

if not auth_header:
token = request.cookies.get(
authentication_constants.AUTHENTICATION_TOKEN_COOKIE_NAME
Expand Down
48 changes: 48 additions & 0 deletions python/ray/dashboard/tests/test_dashboard_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,54 @@ def test_dashboard_request_requires_auth_invalid_token(setup_cluster_with_token_
assert response.status_code == 403


def test_dashboard_request_with_ray_auth_header(setup_cluster_with_token_auth):
"""Test that requests succeed with valid token in X-Ray-Authorization header."""

cluster_info = setup_cluster_with_token_auth
headers = {"X-Ray-Authorization": f"Bearer {cluster_info['token']}"}

response = requests.get(
f"{cluster_info['dashboard_url']}/api/component_activities",
headers=headers,
)

assert response.status_code == 200


def test_authorization_header_takes_precedence(setup_cluster_with_token_auth):
"""Test that standard Authorization header takes precedence over X-Ray-Authorization."""

cluster_info = setup_cluster_with_token_auth

# Provide both headers: valid token in Authorization, invalid in X-Ray-Authorization
headers = {
"Authorization": f"Bearer {cluster_info['token']}",
"X-Ray-Authorization": "Bearer invalid_token_000000000000000000000000",
}

# Should succeed because Authorization header takes precedence
response = requests.get(
f"{cluster_info['dashboard_url']}/api/component_activities",
headers=headers,
)

assert response.status_code == 200

# Now test with invalid Authorization but valid X-Ray-Authorization
headers = {
"Authorization": "Bearer invalid_token_000000000000000000000000",
"X-Ray-Authorization": f"Bearer {cluster_info['token']}",
}

# Should fail because Authorization header takes precedence (even though it's invalid)
response = requests.get(
f"{cluster_info['dashboard_url']}/api/component_activities",
headers=headers,
)

assert response.status_code == 403


def test_dashboard_auth_disabled(setup_cluster_without_token_auth):
"""Test that auth is not enforced when AUTH_MODE is disabled."""

Expand Down