Skip to content

Test metadata.http.oauth2 (OAuth2 Client Credentials for HTTP metadata) #953

@averevki

Description

@averevki

Summary

Test the metadata.http.oauth2 field in AuthPolicy, which enables Authorino to authenticate with external HTTP metadata services using the OAuth2 Client Credentials Grant (RFC 6749).

Docs: https://docs.kuadrant.io/1.4.x/authorino/docs/features/#http-getget-by-post-metadatahttp
Originating PR: Kuadrant/authorino#379 (carried forward to v1beta3 via Kuadrant/authorino#417)

Context

When Authorino fetches external metadata via metadata.http, it can authenticate itself to the metadata server in two ways:

  1. Shared secret (sharedSecretRef) — a static secret passed in the request
  2. OAuth2 Client Credentials (oauth2) — Authorino obtains an access token from a token endpoint and uses it to authenticate

The oauth2 option is currently untested in the testsuite. The MetadataSection.add_http() method in sections.py supports shared_secret_ref but does not yet support oauth2.

Important distinction: This is NOT about identity.oauth2Introspection (which validates incoming user tokens). This feature authenticates Authorino itself when calling external HTTP metadata endpoints.

CRD Spec (metadata.http.oauth2)

From the AuthPolicy/AuthConfig CRD on cluster:

Field Type Required Description
tokenUrl string Yes Token endpoint URL of the OAuth2 resource server
clientId string Yes OAuth2 Client ID
clientSecretRef {name, key} Yes Reference to a K8s Secret key storing the OAuth2 Client Secret
scopes []string No Optional scopes for the client credentials grant
extraParams map[string]string No Optional extra parameters for token requests
cache bool No Cache and reuse the token until expired (default: true). Set to false to force-fetch on every request

When oauth2 and sharedSecretRef are both set, sharedSecretRef is ignored.

The credentials field on metadata.http controls where the obtained access token is placed in the request to the metadata service (defaults to Authorization: Bearer <token>).

Suggested Code Changes

1. Add OAuth2ClientCredentials dataclass

In testsuite/kuadrant/policy/authorization/__init__.py, add a dataclass following the pattern of existing types like Credentials and Cache:

@dataclass
class OAuth2ClientCredentials:
    """OAuth2 Client Credentials Grant configuration for HTTP metadata authentication."""
    tokenUrl: str
    clientId: str
    clientSecretRef: dict[str, str]
    scopes: list[str] = None
    extraParams: dict[str, str] = None
    cache: bool = True

2. Add oauth2 parameter to MetadataSection.add_http()

In testsuite/kuadrant/policy/authorization/sections.py, extend add_http() to accept the new dataclass:

@modify
def add_http(
    self,
    name,
    endpoint,
    method: Literal["GET", "POST"],
    credentials: Credentials = None,
    shared_secret_ref: dict[str, str] = None,
    oauth2: OAuth2ClientCredentials = None,
    **common_features,
):
    """Set metadata http external auth feature"""
    http_config: dict = {
        "url": endpoint,
        "method": method,
        "headers": {"Accept": {"value": "application/json"}},
    }
    if credentials:
        http_config["credentials"] = asdict(credentials)
    if shared_secret_ref:
        http_config["sharedSecretRef"] = shared_secret_ref
    if oauth2:
        http_config["oauth2"] = asdict(oauth2)
    self.add_item(name, {"http": http_config}, **common_features)

3. New test module

Create testsuite/tests/singlecluster/authorino/metadata/test_http_oauth2.py.

Tests should use Keycloak as the OAuth2 token server and Mockserver as the metadata endpoint.

Test setup pattern (following existing metadata tests):

# conftest.py already provides create_client_secret for building K8s Secrets
# with clientID/clientSecret keys (used by Authorino for OAuth2 auth)

@pytest.fixture(scope="module")
def client_secret(create_client_secret, keycloak, blame):
    """Create K8s Secret with Keycloak client credentials for OAuth2"""
    return create_client_secret(blame("oauth2-secret"), keycloak.client.auth_id, keycloak.client.secret)

@pytest.fixture(scope="module")
def mock_metadata_endpoint(request, mockserver, module_label):
    """Mockserver expectation simulating an OAuth2-protected metadata endpoint"""
    request.addfinalizer(lambda: mockserver.clear_expectation(module_label))
    return mockserver.create_response_expectation(
        module_label, {"key": "value"}, ContentType.APPLICATION_JSON
    )

@pytest.fixture(scope="module")
def authorization(authorization, mock_metadata_endpoint, client_secret, keycloak, module_label):
    """Add HTTP metadata with OAuth2 client credentials authentication"""
    authorization.metadata.add_http(
        "oauth2-metadata",
        mock_metadata_endpoint,
        "GET",
        oauth2=OAuth2ClientCredentials(
            tokenUrl=keycloak.well_known["token_endpoint"],
            clientId=keycloak.client.auth_id,
            clientSecretRef={"name": client_secret.name(), "key": "clientSecret"},
        ),
    )
    # Add OPA/response to verify metadata was fetched
    authorization.responses.add_simple("auth.metadata.oauth2-metadata")
    return authorization

Suggested Test Cases

Core Functionality

  1. Basic OAuth2 metadata fetch (GET)
    Verify that Authorino successfully obtains an access token via client credentials grant and uses it to fetch metadata from an OAuth2-protected HTTP endpoint using GET method. Assert 200 and metadata present in response.

  2. OAuth2 metadata fetch (POST)
    Same as above but with method: POST.

  3. OAuth2 with scopes
    Configure scopes: ["email", "profile"] and verify the token request includes them. Use Mockserver or Keycloak to validate scopes are passed.

  4. OAuth2 with extraParams
    Configure extraParams: {"audience": "my-api"} and verify the extra parameters are sent to the token endpoint.

Token Caching

  1. Token caching enabled (default)
    With cache: true (or omitted — the default), send multiple requests and verify that Authorino reuses the access token across requests rather than requesting a new one for each authorization.

  2. Token caching disabled
    Set cache: false and verify Authorino fetches a new access token for every authorization request.

Edge Cases

  1. Invalid client credentials
    Configure oauth2 with wrong clientId or clientSecretRef pointing to a non-existent Secret. Verify the request is denied (expected 403 — metadata fetch failure should result in authorization failure).

Metadata

Metadata

Assignees

No one assigned

    Labels

    AuthorinoChanges related to Authorino Operator and resources it managestest caseNew test case

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    🆕 New

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions