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
- Auth → Social providers — how social IdPs appear on the sign-in page.
- API → Identity providers — HTTP-level reference.