go-signin-with-apple

repository·master·Indexed 42 days ago

https://github.com/timothylock/go-signin-with-apple

A Go library for interacting with Apple's Sign in with Apple REST API. It provides high-level helpers for token validation, revocation, user migration, and webhook handling, including functions for generating client secrets, verifying ID tokens via RS256, and parsing server-to-server notifications.

Tokens
2.1K
Snippets
10
Records
11
Agent score
21%

What's inside go-signin-with-apple

  1. How to choose between VerifyIDToken and GetTypedClaims

    master

    The method you use to process an ID token depends on its source:

    1. From a Client Device (iOS app or Web browser): Use VerifyIDToken. Because the token passed through the client, you must verify its RS256 signature to ensure it hasn't been tampered with. This method fetches Apple's public JWKS and validates iss, aud, and exp.
    2. From Apple's API Response: Use GetTypedClaims. When your server calls VerifyAppToken or VerifyWebToken, Apple returns the id_token directly over TLS. Since the token never touched the client, signature verification is redundant. GetTypedClaims is more efficient and handles Apple's legacy string-based email_verified format.
  2. Configure the Apple Client

    master

    Use apple.NewWithOptions to customize the apple.Client. You can override the HTTP client (for timeouts or testing), the JWKS cache TTL, or the endpoint URLs.

    // Customizing JWKS Cache TTL
    client := apple.NewWithOptions(apple.ClientOptions{
        JWKSCacheTTL: 30 * time.Minute,
    })
    
    // Customizing HTTP Client
    client := apple.NewWithOptions(apple.ClientOptions{
        Client: &http.Client{Timeout: 10 * time.Second},
    })
  3. Verify an ID Token from a Client Device

    master

    Use VerifyIDToken to validate an id_token sent from a client device. This performs a full RS256 signature check using Apple's public keys. The JWKS is cached for 15 minutes by default.

    You can customize the cache duration using apple.NewWithOptions and apple.ClientOptions.JWKSCacheTTL.

    client := apple.New()
    
    claims, err := client.VerifyIDToken(ctx, idToken, clientID)
    if err != nil {
        // token is invalid, expired, or not from Apple
    }
    
    fmt.Println(claims.Subject)        // stable unique user ID
    fmt.Println(claims.Email)          // user's email (if requested)
    fmt.Println(claims.EmailVerified)  // bool
    fmt.Println(claims.IsPrivateEmail) // bool — true if Apple private relay address
    fmt.Println(claims.RealUserStatus) // 0=unsupported, 1=unknown, 2=likelyReal (iOS 14+)
  4. Generate a Client Secret

    master

    Apple requires a signed JWT as a client secret for every request. Use apple.GenerateClientSecret to create one. You will need your Team ID, Services ID (or Bundle ID), a 10-character Key ID, and the contents of your .p8 private key file.

    The resulting secret is a JWT valid for 180 days; you must generate a new one before it expires.

    secret, err := apple.GenerateClientSecret(signingKey, teamID, clientID, keyID)
  5. Migrate Users during App Transfers

    master

    When an app is transferred to a new developer team, Apple provides a transfer_sub identifier. Use GetUserMigrationInfo to exchange this identifier for the user's new stable identifier under your team's credentials. Apple allows a 60-day window where both teams' credentials remain valid.

    var resp apple.UserMigrationResponse
    
    err := client.GetUserMigrationInfo(ctx, apple.UserMigrationRequest{
        ClientID:     clientID,     // recipient team's Services ID
        ClientSecret: secret,       // recipient team's client secret
        TransferSub:  transferSub,  // provided by the original team
    }, &resp)
    
    fmt.Println(resp.Sub)   // new stable user ID under your team
    fmt.Println(resp.Email)
  6. Validate an Authorization Code (App or Web)

    master

    To exchange an authorization code received from Apple for tokens, create an apple.Client and use VerifyAppToken (for iOS apps) or VerifyWebToken (for web flows).

    Important: Apple returns errors in the response body with a 400 status rather than returning a Go error. Always check resp.Error before processing the response.

    client := apple.New()
    
    // iOS app token
    err := client.VerifyAppToken(ctx, apple.AppValidationTokenRequest{
        ClientID:     clientID,
        ClientSecret: secret,
        Code:         authorizationCode,
    }, &resp)
    
    // Web token
    err := client.VerifyWebToken(ctx, apple.WebValidationTokenRequest{
        ClientID:     clientID,
        ClientSecret: secret,
        Code:         authorizationCode,
        RedirectURI:  "https://example.com/callback",
    }, &resp)
  7. Revoke Access Tokens or Refresh Tokens

    master

    To invalidate a token (e.g., during user logout or account deletion), use RevokeAccessToken or RevokeRefreshToken. A successful revocation returns HTTP 200 with no body; check resp.Error for any failures.

    // Revoke Access Token
    err := client.RevokeAccessToken(ctx, apple.RevokeAccessTokenRequest{
        ClientID:     clientID,
        ClientSecret: secret,
        AccessToken:  accessToken,
    }, &resp)
  8. Handle Server-to-Server Notifications

    master

    Apple sends signed JWT notifications to your registered webhook URL when events like consent-revoked or account-delete occur. Use ParseServerNotification to verify the signature and parse the payload.

    client := apple.New()
    
    http.HandleFunc("/apple/notifications", func(w http.ResponseWriter, r *http.Request) {
        notification, err := client.ParseServerNotification(r.Context(), r.FormValue("payload"))
        if err != nil {
            w.WriteHeader(http.StatusBadRequest)
            return
        }
    
        switch notification.Events.Type {
        case "consent-revoked":
            // User revoked Sign in with Apple for your app
        case "account-delete":
            // User deleted their Apple ID — you must delete all their data within 30 days
        }
    })