> ## Documentation Index
> Fetch the complete documentation index at: https://www.c1.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication recipes

> Battle-tested patterns for authenticating with target system APIs.

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:**

```go theme={"theme":{"light":"css-variables","dark":"css-variables"}}
// 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:**

```go theme={"theme":{"light":"css-variables","dark":"css-variables"}}
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:**

```go theme={"theme":{"light":"css-variables","dark":"css-variables"}}
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:**

```go theme={"theme":{"light":"css-variables","dark":"css-variables"}}
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.
