Add valid_scopes and extra_authorize_params to WorkOSProvider (#4135)

This commit is contained in:
Tiago Surjus Kaneta 2026-05-20 10:31:48 -04:00 committed by GitHub
commit b0fb2c3ae6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 89 additions and 0 deletions

View file

@ -168,6 +168,7 @@ class WorkOSProvider(OAuthProxy):
issuer_url: AnyHttpUrl | str | None = None,
redirect_path: str | None = None,
required_scopes: list[str] | None = None,
valid_scopes: list[str] | None = None,
timeout_seconds: int = 10,
allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
@ -176,6 +177,7 @@ class WorkOSProvider(OAuthProxy):
consent_csp_policy: str | None = None,
forward_resource: bool = True,
fallback_refresh_token_expiry_seconds: int | None = None,
extra_authorize_params: dict[str, str] | None = None,
http_client: httpx.AsyncClient | None = None,
enable_cimd: bool = True,
):
@ -192,6 +194,10 @@ class WorkOSProvider(OAuthProxy):
to avoid 404s during discovery when mounting under a path.
redirect_path: Redirect path configured in WorkOS (defaults to "/auth/callback")
required_scopes: Required OAuth scopes (no default)
valid_scopes: All scopes that clients are allowed to request, advertised through
well-known endpoints. Defaults to required_scopes if not provided. Use this
when you want clients to be able to request additional scopes beyond the
required minimum.
timeout_seconds: HTTP request timeout for WorkOS API calls (defaults to 10)
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
@ -207,6 +213,9 @@ class WorkOSProvider(OAuthProxy):
When "external", the built-in consent screen is skipped but no warning is
logged, indicating that consent is handled externally (e.g. by the upstream IdP).
SECURITY WARNING: Only set to False for local development or testing environments.
extra_authorize_params: Additional parameters to forward to WorkOS's authorization endpoint.
Useful for forcing scopes like `offline_access` so WorkOS issues a refresh token,
e.g. ``{"scope": "openid profile email offline_access"}``.
http_client: Optional httpx.AsyncClient for connection pooling in token verification.
When provided, the client is reused across verify_token calls and the caller
is responsible for its lifecycle. When None (default), a fresh client is created per call.
@ -221,6 +230,9 @@ class WorkOSProvider(OAuthProxy):
scopes_final = (
parse_scopes(required_scopes) if required_scopes is not None else []
)
valid_scopes_final = (
parse_scopes(valid_scopes) if valid_scopes is not None else None
)
# Create WorkOS token verifier
token_verifier = WorkOSTokenVerifier(
@ -248,6 +260,8 @@ class WorkOSProvider(OAuthProxy):
consent_csp_policy=consent_csp_policy,
forward_resource=forward_resource,
fallback_refresh_token_expiry_seconds=fallback_refresh_token_expiry_seconds,
extra_authorize_params=extra_authorize_params,
valid_scopes=valid_scopes_final,
enable_cimd=enable_cimd,
)

View file

@ -103,6 +103,81 @@ class TestWorkOSProvider:
assert provider._redirect_path == "/auth/callback"
# WorkOS provider has no default scopes but we can't easily verify without accessing internals
def test_extra_authorize_params_default_none(self, memory_storage: MemoryStore):
"""WorkOS doesn't set provider-specific defaults — empty unless caller opts in."""
provider = WorkOSProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="https://test.authkit.app",
base_url="https://myserver.com",
jwt_signing_key="test-secret",
client_storage=memory_storage,
)
assert provider._extra_authorize_params == {}
def test_extra_authorize_params_passed_through(self, memory_storage: MemoryStore):
"""Caller-supplied params are forwarded to the upstream authorize URL.
Common use case: force `offline_access` into the scope so WorkOS issues
a refresh token.
"""
provider = WorkOSProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="https://test.authkit.app",
base_url="https://myserver.com",
jwt_signing_key="test-secret",
client_storage=memory_storage,
extra_authorize_params={
"scope": "openid profile email offline_access",
},
)
assert provider._extra_authorize_params == {
"scope": "openid profile email offline_access",
}
def test_valid_scopes_passed_through(self, memory_storage: MemoryStore):
"""valid_scopes is forwarded to OAuthProxy and advertised via DCR."""
provider = WorkOSProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="https://test.authkit.app",
base_url="https://myserver.com",
required_scopes=["openid"],
valid_scopes=["openid", "profile", "email", "offline_access"],
jwt_signing_key="test-secret",
client_storage=memory_storage,
)
reg_options = provider.client_registration_options
assert reg_options is not None
assert reg_options.valid_scopes is not None
assert set(reg_options.valid_scopes) == {
"openid",
"profile",
"email",
"offline_access",
}
def test_valid_scopes_defaults_to_required(self, memory_storage: MemoryStore):
"""When valid_scopes is omitted, the OAuthProxy falls back to required_scopes."""
provider = WorkOSProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="https://test.authkit.app",
base_url="https://myserver.com",
required_scopes=["openid", "profile"],
jwt_signing_key="test-secret",
client_storage=memory_storage,
)
reg_options = provider.client_registration_options
assert reg_options is not None
assert reg_options.valid_scopes is not None
assert set(reg_options.valid_scopes) == {"openid", "profile"}
def test_oauth_endpoints_configured_correctly(self, memory_storage: MemoryStore):
"""Test that OAuth endpoints are configured correctly."""
provider = WorkOSProvider(