How Bag Endpoint Resolution Works in ipatool After Initial Authentication

Bag endpoint resolution in ipatool fetches a dynamic XML configuration from Apple's servers to determine the current authentication URL, validates it against strict security rules, then uses that resolved endpoint for all subsequent login requests.

After a user initiates authentication in ipatool, the tool must discover where to send the actual login credentials. Rather than hardcoding Apple's authentication URL, ipatool implements a bag-based endpoint resolution system that adapts to Apple's changing infrastructure. This article explains how the majd/ipatool repository implements this flow, with direct references to the source code.

What Is the Bag in ipatool?

The bag is a small XML payload retrieved from Apple's PrivateInit service that contains the current SAP (Secure Apple Payments) configuration. This configuration includes the live authentication endpoint URL, SAP setup endpoints, and version information. By fetching this bag at runtime, ipatool can automatically follow Apple's latest endpoint assignments without requiring a code update.

The Four-Step Bag Resolution Process

Step 1: Generate a Machine-Specific GUID

The resolution process begins in pkg/appstore/appstore_bag.go where appstore.Bag generates a unique identifier for the client machine.

// appstore_bag.go:22-31
func (t *appstore) Bag(input BagInput) (BagOutput, error) {
    macAddr, err := t.machine.MacAddress()
    if err != nil {
        return BagOutput{}, err
    }

    guid := machineIdentity(macAddr)
    // ...
}

The machine.MacAddress() call retrieves the device's MAC address, which machineIdentity(macAddr) transforms into a GUID that uniquely identifies this client to Apple's servers.

Step 2: Fetch the Bag from Apple's PrivateInit Service

With the GUID generated, ipatool constructs and executes a request to Apple's bag endpoint:

// appstore_bag.go:35-45
req, err := t.bagRequest(guid)
if err != nil {
    return BagOutput{}, err
}

res, err := t.bagClient.Send(req)
if err != nil {
    return BagOutput{}, err
}

sapConfig := parseSAPConfig(res.Data.URLBag)

The bagRequest function builds a URL using the PrivateInitDomain and PrivateInitPath constants, appending the GUID as a query parameter. The response contains res.Data.URLBag, which parseSAPConfig extracts into a structured SAPConfig containing:

  • AuthEndpoint — the target authentication URL
  • SAPSetupEndpoint — endpoint for SAP handshake setup
  • SAPSetupCertEndpoint — certificate retrieval endpoint
  • SAPVersion — protocol version identifier

Step 3: Validate the Authentication Endpoint

Before trusting the retrieved URL, ipatool runs strict validation through validateSAPConfig and validateAuthenticationEndpoint:

// appstore_bag.go:76-84
func validateAuthenticationEndpoint(url url.URL) error {
    if url.Scheme != "https" {
        return fmt.Errorf("invalid scheme: %s", url.Scheme)
    }

    if url.Host != PrivateAppStoreAPIDomain && !strings.HasSuffix(url.Host, "-buy.itunes.apple.com") {
        return fmt.Errorf("invalid host: %s", url.Host)
    }

    if url.Path != PrivateAppStoreAPIPathAuth {
        return fmt.Errorf("invalid path: %s", url.Path)
    }

    return nil
}

The validation enforces three security requirements:

  • HTTPS only — Rejects any non-encrypted endpoint
  • Trusted hosts only — Must match PrivateAppStoreAPIDomain or end with -buy.itunes.apple.com
  • Exact path match — Must equal /WebObjects/MZFinance.woa/wa/authenticate (PrivateAppStoreAPIPathAuth)

If any check fails, authentication aborts immediately with a descriptive error.

Step 4: Use the Resolved Endpoint for Login

The validated AuthEndpoint is then passed through to the login flow. In pkg/appstore/appstore_login.go, the Login method retrieves the bag and uses its configuration:

// appstore_login.go:49-58
bagResult, err := t.bag(BagInput{})
if err != nil {
    return LoginOutput{}, err
}

authOptions, err := t.authOptions(AuthOptionsInput{Guid: guid})
if err != nil {
    return LoginOutput{}, err
}

// The login request is ultimately sent to bagResult.SAPConfig.AuthEndpoint

The login helper receives bag.SAPConfig.AuthEndpoint and uses that exact URL for the authentication POST request, ensuring the client always communicates with Apple's current designated server.

Why Bag Endpoint Resolution Matters

Dynamic adaptation — Apple can rotate authentication URLs or migrate to new domains at any time. By fetching the bag each session, ipatool automatically follows these changes without requiring a client update or user intervention.

Security guarantees — The multi-layer validation in validateAuthenticationEndpoint protects against man-in-the-middle attacks, malicious redirects, and configuration tampering. Only cryptographically verified, path-specific HTTPS endpoints from Apple's infrastructure are trusted.

Version safety — validateSAPConfig currently enforces SAP version 200 support. If Apple introduces a newer protocol version, ipatool fails fast with a clear error, alerting developers that the client requires updates rather than silently breaking or behaving unpredictably.

Practical Code Example

Here's how you can observe the bag resolution process in your own code using the ipatool package:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/majd/ipatool/v2/pkg/appstore"
)

func main() {
    // Initialize the appstore client
    client, err := appstore.New(appstore.Config{
        // ... configuration
    })
    if err != nil {
        log.Fatalf("failed to create client: %v", err)
    }

    // Retrieve the bag to see the resolved configuration
    bagOut, err := client.Bag(appstore.BagInput{})
    if err != nil {
        log.Fatalf("cannot get bag: %v", err)
    }

    fmt.Println("Resolved authentication endpoint:", bagOut.SAPConfig.AuthEndpoint)
    fmt.Println("SAP Setup endpoint:", bagOut.SAPConfig.SAPSetupEndpoint)
    fmt.Println("SAP Version:", bagOut.SAPConfig.SAPVersion)

    // The Login method automatically uses bagOut.SAPConfig.AuthEndpoint
    loginOut, err := client.Login(appstore.LoginInput{
        Email:    "user@example.com",
        Password: "your-password",
    })
    if err != nil {
        log.Fatalf("login failed: %v", err)
    }

    fmt.Println("Login successful, Apple ID:", loginOut.AccountInfo.AppleID)
}

Key Source Files

File Responsibility
pkg/appstore/appstore_bag.go GUID generation, bag retrieval, SAP config parsing, endpoint validation
pkg/appstore/appstore_login.go Orchestrates bag fetching and passes resolved endpoint to login request
pkg/appstore/action_signer.go Defines SAPConfig struct holding AuthEndpoint and related fields
pkg/appstore/appstore.go Core interface wiring bag and login methods together

Summary

  • Bag endpoint resolution in ipatool replaces hardcoded URLs with dynamic discovery from Apple's PrivateInit service
  • The process generates a machine GUID, fetches the XML bag, validates the authentication URL, then uses it for login
  • Security validation enforces HTTPS, trusted host suffixes, and exact path matching in pkg/appstore/appstore_bag.go
  • This design ensures ipatool adapts automatically to Apple's infrastructure changes while maintaining strict security boundaries

Frequently Asked Questions

What happens if Apple changes their authentication URL?

ipatool automatically adapts. Because it fetches the current AuthEndpoint from the bag during each authentication session, any URL change Apple deploys takes effect immediately without requiring a client update. The validation logic ensures the new URL still meets security requirements.

Why does ipatool validate the authentication endpoint so strictly?

The validation in validateAuthenticationEndpoint prevents man-in-the-middle attacks and malicious redirects. By requiring HTTPS, specific host patterns (-buy.itunes.apple.com or the private domain), and an exact path match, ipatool ensures credentials are only sent to genuine Apple infrastructure even if the bag response were somehow tampered with.

What is SAP version 200 and why is it checked?

SAP (Secure Apple Payments) is Apple's encrypted communication protocol. Version 200 is the current protocol revision supported by ipatool as implemented in validateSAPConfig. The version check ensures that if Apple deploys a newer incompatible protocol, ipatool fails explicitly rather than attempting broken communication.

Can I use the bag endpoint resolution without calling Login?

Yes. You can call appStore.Bag(BagInput{}) directly to retrieve the current SAPConfig and inspect the resolved endpoints. However, the Login method internally performs this step automatically, so manual bag retrieval is primarily useful for debugging or monitoring infrastructure changes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →