Identity providers

An identity provider (IdP) is an external sign-in source Huudis federates to — Google, Microsoft, an enterprise SAML directory, a corporate OIDC issuer. Configuring an IdP lets your members log in through the upstream provider. The Go SDK exposes them on client.IdentityProviders. For the HTTP surface see API → Identity providers; for the protocol flow see Auth → Social providers.

Field on the Client

client.IdentityProviders — type *huudis.IdentityProvidersResource. There's no Get — List ships the full record for every IdP, and most workspaces have at most a handful.

Methods

List

Signature. func (r *IdentityProvidersResource) List(ctx context.Context, auth RequestAuth) ([]IdentityProvider, error)

Returns every IdP configured for the active workspace. Includes both enabled and disabled rows — filter on Enabled client-side.

idps, err := client.IdentityProviders.List(ctx, huudis.RequestAuth{})
if err != nil {
    return err
}
for _, p := range idps {
    state := "off"
    if p.Enabled {
        state = "on"
    }
    fmt.Println(p.ID, p.Kind, p.Name, state)
}

Create

Signature. func (r *IdentityProvidersResource) Create(ctx context.Context, in CreateIdentityProviderInput, auth RequestAuth) (*IdentityProvider, error)

Registers a new IdP. The Kind field decides what Metadata Huudis expects:

  • Kind: "oidc" — {"issuer", "clientId", "clientSecret", "scopes"}
  • Kind: "saml" — {"entityId", "ssoUrl", "certificatePem", "signatureAlgorithm"}
  • Kind: "social" — {"provider": "google" | "github" | "microsoft", "clientId", "clientSecret"}
idp, err := client.IdentityProviders.Create(ctx, huudis.CreateIdentityProviderInput{
    Kind: "oidc",
    Name: "Acme Corp SSO",
    Metadata: map[string]any{
        "issuer":       "https://sso.acme.example.com",
        "clientId":     "huudis-prod",
        "clientSecret": os.Getenv("ACME_OIDC_SECRET"),
        "scopes":       []string{"openid", "profile", "email", "groups"},
    },
}, huudis.RequestAuth{})
if err != nil {
    return err
}
log.Println(idp.ID)

The IdP starts enabled. PATCH enabled: false to keep the configuration without exposing it on the sign-in page.

Update

Signature. func (r *IdentityProvidersResource) Update(ctx context.Context, id string, patch map[string]any, auth RequestAuth) (*IdentityProvider, error)

PATCH — only keys you pass change. The wrapped metadata map follows top-level-merge: a partial metadata keeps omitted keys at their old values. To clear a metadata key, pass nil.

// Rotate the OIDC client secret.
_, _ = client.IdentityProviders.Update(ctx, "idp_01HX...", map[string]any{
    "metadata": map[string]any{"clientSecret": rotated},
}, huudis.RequestAuth{})

// Disable temporarily without dropping config.
_, _ = client.IdentityProviders.Update(ctx, "idp_01HX...", map[string]any{
    "enabled": false,
}, huudis.RequestAuth{})

Delete

Signature. func (r *IdentityProvidersResource) Delete(ctx context.Context, id string, auth RequestAuth) error

Removes the IdP. Existing sessions seeded by it stay alive until they expire; subsequent sign-ins via that IdP fail. LinkedAccount rows (connecting a Huudis user to an upstream subject) are kept for audit but become inert.

if err := client.IdentityProviders.Delete(ctx, "idp_01HX...", huudis.RequestAuth{}); err != nil {
    return err
}

Types

type IdentityProvider struct {
    ID        string         `json:"id"`        // "idp_..."
    AccountID string         `json:"accountId"`
    Kind      string         `json:"kind"`      // "oidc" | "saml" | "social"
    Name      string         `json:"name"`
    Enabled   bool           `json:"enabled"`
    Metadata  map[string]any `json:"metadata,omitempty"` // shape depends on kind
    CreatedAt string         `json:"createdAt"` // ISO-8601
}

Full per-kind schema in API → Identity providers.

Common patterns

Set up Google SSO for a workspace

_, err := client.IdentityProviders.Create(ctx, huudis.CreateIdentityProviderInput{
    Kind: "social",
    Name: "Google",
    Metadata: map[string]any{
        "provider":     "google",
        "clientId":     os.Getenv("GOOGLE_CLIENT_ID"),
        "clientSecret": os.Getenv("GOOGLE_CLIENT_SECRET"),
    },
}, huudis.RequestAuth{})
// Now the workspace's sign-in page shows a "Sign in with Google" button.

SAML round-trip

SAML metadata is bulky — fetch the upstream XML and pass it as one blob:

xml, _ := fetchURL(ctx, "https://sso.acme.com/saml/metadata")
entityID, ssoURL, certPem := parseSamlMetadata(xml)

_, err := client.IdentityProviders.Create(ctx, huudis.CreateIdentityProviderInput{
    Kind: "saml",
    Name: "Acme SAML",
    Metadata: map[string]any{
        "entityId":       entityID,
        "ssoUrl":         ssoURL,
        "certificatePem": certPem,
    },
}, huudis.RequestAuth{})

Rotate IdP credentials safely

For an OIDC IdP, mint the new client secret upstream first, then PATCH:

func rotateOidcSecret(ctx context.Context, c *huudis.Client, idpID, freshSecret string) error {
    _, err := c.IdentityProviders.Update(ctx, idpID, map[string]any{
        "metadata": map[string]any{"clientSecret": freshSecret},
    }, huudis.RequestAuth{})
    // After confirming new sign-ins work, revoke the old secret upstream.
    return err
}

There's no overlap window inside Huudis — the new secret takes effect immediately. Plan the upstream rotation accordingly.

Discover which IdP a user came from

The LinkedAccount rows on client.Account.Linked.List(...) carry a Provider field that matches the IdP's ID (or a built-in social slug). Cross-reference to attribute sign-ins:

linked, _ := client.Account.Linked.List(ctx, huudis.RequestAuth{})
idps, _ := client.IdentityProviders.List(ctx, huudis.RequestAuth{})
byID := map[string]huudis.IdentityProvider{}
for _, p := range idps {
    byID[p.ID] = p
}
for _, l := range linked {
    name := l.Provider
    if p, ok := byID[l.Provider]; ok {
        name = p.Name
    }
    fmt.Println(l.Email, "→", name)
}

Errors

Code Status Cause
validation_error 400 Missing required metadata field for the chosen Kind, malformed certificate, unknown provider.
not_found 404 IdP doesn't exist or lives in another workspace.
conflict 409 Name already taken in this workspace.
state_conflict 409 Tried to change Kind on an existing IdP (immutable).
forbidden 403 Caller isn't an admin/owner.

Next

Huudis Docs · Identity providers | Huudis