Skip to main content
Each recipe includes the problem, solution code, and rationale.

API key authentication

Problem: Connect to an API that uses API key in the Authorization header. Solution:
// pkg/client/client.go
import "github.com/conductorone/baton-sdk/pkg/uhttp"

func NewClient(ctx context.Context, apiKey string) (*Client, error) {
    httpClient, err := uhttp.NewBaseHttpClient(ctx,
        uhttp.WithBearerToken(apiKey))
    if err != nil {
        return nil, err
    }
    return &Client{http: httpClient, baseURL: "https://api.example.com"}, nil
}
Why: The SDK’s uhttp package handles retries and rate limiting automatically. WithBearerToken sets Authorization: Bearer <token>.

OAuth2 client credentials

Problem: Exchange client ID and secret for an access token. Solution:
import (
    "golang.org/x/oauth2/clientcredentials"
)

func NewClient(ctx context.Context, clientID, clientSecret, tokenURL string) (*Client, error) {
    config := &clientcredentials.Config{
        ClientID:     clientID,
        ClientSecret: clientSecret,
        TokenURL:     tokenURL,
        Scopes:       []string{"read", "write"},
    }

    // This client automatically refreshes tokens
    httpClient := config.Client(ctx)

    return &Client{http: httpClient}, nil
}
Why: The clientcredentials package handles token refresh automatically. You don’t need to manage token expiry.

JWT service account (Google-style)

Problem: Authenticate with a service account JSON key file. Solution:
import (
    "google.golang.org/api/option"
    admin "google.golang.org/api/admin/directory/v1"
)

func NewGoogleClient(ctx context.Context, credentialsJSON []byte, adminEmail string) (*admin.Service, error) {
    // Parse the service account key
    config, err := google.JWTConfigFromJSON(credentialsJSON,
        admin.AdminDirectoryUserReadonlyScope,
        admin.AdminDirectoryGroupReadonlyScope,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to parse credentials: %w", err)
    }

    // Impersonate a domain admin for domain-wide access
    config.Subject = adminEmail

    // Create the service
    return admin.NewService(ctx, option.WithHTTPClient(config.Client(ctx)))
}
Why: Domain-wide delegation requires impersonating a domain admin. The Subject field specifies which user to impersonate.

LDAP bind

Problem: Connect to Active Directory or LDAP server. Solution:
import "github.com/go-ldap/ldap/v3"

func NewLDAPClient(ctx context.Context, serverURL, bindDN, bindPassword string) (*ldap.Conn, error) {
    conn, err := ldap.DialURL(serverURL) // ldaps://dc.example.com:636
    if err != nil {
        return nil, fmt.Errorf("failed to connect to LDAP: %w", err)
    }

    // Simple bind with username/password
    err = conn.Bind(bindDN, bindPassword)
    if err != nil {
        conn.Close()
        return nil, fmt.Errorf("failed to bind: %w", err)
    }

    return conn, nil
}

// For Kerberos/GSSAPI (domain-joined machines)
func NewLDAPClientKerberos(ctx context.Context, serverURL string) (*ldap.Conn, error) {
    conn, err := ldap.DialURL(serverURL)
    if err != nil {
        return nil, err
    }

    err = conn.ExternalBind()
    if err != nil {
        conn.Close()
        return nil, err
    }

    return conn, nil
}
Why: LDAP requires binding before any queries. Simple bind uses credentials; external bind uses OS-level Kerberos tickets.

Next