Skip to content

OAuth callback URL uses application name instead of external hostname when external_hostname config not set #413

Description

@dmvdm

Note: This issue was generated with AI assistance (GitHub Copilot) based on automated log analysis and triage.
Filed by @canonical/solutions-qa

Summary

When external_hostname config is not set and an oauth relation is established, the charm constructs an invalid OAuth redirect URL using the Juju application name instead of a routable external hostname, causing OAuth configuration to fail with "Invalid URL" error.

Test Observer Details

Error Details

unit-target-0: 2026-01-18 10:22:50 ERROR unit.target/0.juju-log oauth:23: 
Invalid OAuth client config: Invalid URL https://target/auth/oidc/callback

The charm constructs the redirect URL as https://target/auth/oidc/callback where "target" is the Juju application name. This fails URL validation in the OAuth library because "target" is not a valid FQDN (lacks a TLD like .com or .local).

Root Cause

File: src/charm.py#L280

def _get_external_hostname(self) -> str:
    """Extract and return hostname from site_url or default to [application name].
    
    Returns:
        The site hostname defined as part of the site_url configuration or a default value.
    """
    return (
        typing.cast(str, self.config["external_hostname"])
        if self.config["external_hostname"]
        else self.app.name  # ← BUG: Defaults to application name
    )

This method is called by OAuthObserver._generate_client_config() in src/oauth_observer.py#L81 to construct the OAuth redirect URI:

self.client_config = ClientConfig(
    redirect_uri=f"https://{self._external_hostname_callback()}/auth/oidc/callback",
    # ...
)

The OAuth library validates URLs using a regex pattern that requires domain names to have at least one dot (e.g., example.com, discourse.local). Single-word hostnames like "target" are rejected.

Validation Code: lib/charms/hydra/v0/oauth.py

Impact

  • Integration tests fail: All test deployments without explicit external_hostname config fail
  • Misleading error symptoms: The test shows "database-relation-changed hook failed" but the real error is OAuth configuration
  • Silent failure in production: Users deploying with oauth relation but no external_hostname config will get blocked status with unclear error message
  • Affects current main branch: Bug still exists in latest code (commit 1420720)

Expected Behavior

The charm should handle missing external_hostname config gracefully when oauth relation is present. Options:

  1. Require configuration (simplest):

    if self.model.get_relation(OAUTH_RELATION_NAME) and not self.config["external_hostname"]:
        self.unit.status = BlockedStatus("external_hostname config required for OAuth")
        return
  2. Derive from ingress relation (better UX):

    • The charm already has nginx-route relation which provides external routing
    • Could extract hostname from ingress relation data instead of using app name
    • Falls back to blocking if neither config nor ingress provides valid hostname
  3. Validate before use:

    • Check that self.app.name matches URL requirements before using as default
    • Block with clear message if validation fails

Reproduction

Deploy discourse-k8s with oauth and nginx-route relations but without external_hostname config:

applications:
  target:
    charm: discourse-k8s
    channel: latest/edge
    # external_hostname NOT configured
  hydra:
    charm: hydra
  content-cache-k8s:
    charm: content-cache-k8s
relations:
  - [hydra:oauth, target:oauth]
  - [content-cache-k8s:nginx-proxy, target:nginx-route]

Result: Charm enters error state with "Invalid OAuth client config: Invalid URL https://target/auth/oidc/callback"

Additional Context

  • The database hook failure shown in juju status is a symptom, not the root cause
  • OAuth error occurs first (10:22:50), database hook fails later as a consequence
  • Other charms using the OAuth library would have the same issue if they default to application name

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions