# How Bag Endpoint Resolution Works in ipatool After Initial Authentication

> Discover how ipatool resolves bag endpoints after authentication by fetching and validating dynamic XML configurations from Apple servers for secure login requests.

- Repository: [Majd/ipatool](https://github.com/majd/ipatool)
- Tags: internals
- Published: 2026-09-06

---

**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`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore_bag.go) where `appstore.Bag` generates a unique identifier for the client machine.

```go
// 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:

```go
// 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`:

```go
// 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`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore_login.go), the `Login` method retrieves the bag and uses its configuration:

```go
// 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:

```go
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`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore_bag.go) | GUID generation, bag retrieval, SAP config parsing, endpoint validation |
| [`pkg/appstore/appstore_login.go`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore_login.go) | Orchestrates bag fetching and passes resolved endpoint to login request |
| [`pkg/appstore/action_signer.go`](https://github.com/majd/ipatool/blob/main/pkg/appstore/action_signer.go) | Defines `SAPConfig` struct holding `AuthEndpoint` and related fields |
| [`pkg/appstore/appstore.go`](https://github.com/majd/ipatool/blob/main/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`](https://github.com/majd/ipatool/blob/main/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.