Skip to content

ProviderConfig does not support custom IDP origin for UAA password grant #204

Description

@Sim-Jar

Describe the bug

When using a custom Identity Provider (IDP) with SAP BTP Cloud Foundry, the UAA token endpoint requires a login_hint parameter to route the password grant to the correct IDP origin. The underlying go-cfclient/v3 library already supports this via config.Origin(origin), which appends ?login_hint={"origin":"<value>"} to the token URL. However, the provider's CfCredentials struct and GetCredentialConfig function do not expose or pass this parameter.

This means all CF resources fail with invalid_grant: User authentication failed when the credentials belong to a user from a custom IDP (e.g., SAP IAS with a custom tenant).

Tested with Provider Version

v0.3.3

To Reproduce

  1. Configure a ProviderConfig with credentials for a user that authenticates via a custom IDP (not sap.ids):
    {
      "email": "user@example.com",
      "password": "secret"
    }
  2. Create any CF managed resource (e.g., Organization with Observe management policy)
  3. The resource fails to sync with:
    cannot create new client: cannot config cloudfoundry client: 
    oauth2: "invalid_client" "{\"error\":\"invalid_grant\",\"error_description\":\"User authentication failed.\"}"
    

Expected behavior

The provider should support an origin field in the credentials secret (or in the ProviderConfig spec) and pass it to the go-cfclient library via config.Origin(). This would allow authentication with custom IDPs.

Root Cause Analysis

The go-cfclient/v3 library already fully supports this. In config/options.go:

func Origin(origin string) Option {
    return func(c *Config) error {
        c.origin = origin
        return nil
    }
}

And in config/config.go, when origin is set, it appends login_hint to the token URL:

if c.origin != "" {
    authConfig.Endpoint.TokenURL = addLoginHintToURL(authConfig.Endpoint.TokenURL, c.origin)
}

However, the provider does not use this. In internal/clients/providerconfig.go:

// CfCredentials has no origin field
type CfCredentials struct {
    Email    string `json:"email"`
    Username string `json:"username"`
    Password string `json:"password"`
    Passcode string `json:"passcode"`
}

// GetCredentialConfig never passes config.Origin()
func GetCredentialConfig(...) (*config.Config, error) {
    // ...
    return config.New(*url, config.UserPassword(cred.Email, cred.Password), config.SkipTLSValidation())
}

Suggested Fix

Two changes in internal/clients/providerconfig.go:

  1. Add Origin to the credentials struct:

    type CfCredentials struct {
        Email    string `json:"email"`
        Username string `json:"username"`
        Password string `json:"password"`
        Passcode string `json:"passcode"`
        Origin   string `json:"origin"`
    }
  2. Pass it when creating the client:

    opts := []config.Option{
        config.UserPassword(cred.Email, cred.Password),
        config.SkipTLSValidation(),
    }
    if cred.Origin != "" {
        opts = append(opts, config.Origin(cred.Origin))
    }
    return config.New(*url, opts...)

This would cause the UAA token request to include ?login_hint={"origin":"<value>"}, correctly routing authentication to the custom IDP.

Additional context

  • SAP BTP global accounts using SAP IAS as a custom IDP are affected — this is the recommended setup for production environments
  • The cf CLI handles this via cf login --origin <origin>, which uses the same login_hint mechanism
  • The BTP Crossplane provider (sap/crossplane-provider-btp) supports custom IDPs via an idp field in the credentials secret (added in v1.6.0, PR #466)
  • This is a backwards-compatible change: the origin field in the credentials JSON is optional, and existing users without custom IDP are not affected

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