A Swift API for signing in to Apple's developer services.
XcodesLoginKit handles Apple's SRP password login, federated Apple IDs, two-factor authentication, session validation, logout, and optional fastlane/Spaceship cookie import.
Add the package to your Package.swift:
.package(url: "https://github.com/XcodesOrg/XcodesLoginKit", branch: "main")
Then add the product you need:
.product(name: "XcodesLoginKit", package: "XcodesLoginKit")
Use Client when your app wants to drive its own sign-in UI:
import Foundation import XcodesLoginKit let client = Client() let state = try await client.authenticationState( accountName: "[email protected]", password: password ) switch state { case .authenticated(let session): print("Signed in as \(session.user.fullName ?? "Apple Developer")") case .waitingForFederatedAuthentication(let federation): guard let idpURL = federation.idpURL else { throw AuthenticationError.federatedAuthenticationRequired } // Open idpURL in a browser. After the user signs in, collect the redirected URL. let callbackURLString = "... pasted or received callback URL ..." try await client.validateFederatedCallbackURLString(callbackURLString) case .waitingForSecondFactor(let option, let authOptions, let sessionData): switch option { case .codeSent: let code = "... code from trusted device ..." try await client.submitSecurityCode(.device(code: code), sessionData: sessionData) case .smsPendingChoice: guard let phoneNumber = authOptions.trustedPhoneNumbers?.first else { return } try await client.requestSMSSecurityCode( to: phoneNumber, authOptions: authOptions, sessionData: sessionData ) case .smsSent(let phoneNumber): let code = "... code from SMS ..." try await client.submitSecurityCode( .sms(code: code, phoneNumberId: phoneNumber.id), sessionData: sessionData ) case .securityKey: // Add the XcodesLoginKitSecurityKey product and call // submitSecurityKeyPinCode(_:sessionData:authOptions:). break } case .unauthenticated, .notAppleDeveloper: break }
Client resolves Apple's public sign-in widget key in this order:
- An explicit key supplied by the application, when present.
- A key previously saved in XcodesLoginKit's in-memory or on-disk cache.
- The
widgetKeyin App Store Connect's unauthenticated/logoutredirect. - The legacy App Store Connect Olympus configuration endpoint as a final fallback.
The sign-out lookup uses a separate cookie-free session and does not follow the redirect. Following that redirect would perform a real sign-out, so the authentication session is never used for this request.
Supply an explicit key without changing the library:
let client = Client(serviceKeyProvider: .fixed("current-public-widget-key"))
For dynamic configuration, supply an asynchronous, Sendable loader:
let client = Client( serviceKeyProvider: AppleServiceKeyProvider { try await configuration.appleServiceKey() } )
Successful automatic lookups are cached on a best-effort basis. Cache read or write failures do not
block authentication. If every network source fails, the client throws
AuthenticationError.serviceKeyResolutionFailed(attempts:), whose localized description includes
the source-specific failures and HTTP status codes when available.
- Create a
Client. Pass a customURLSessionif you want isolated cookie storage. - Call
authenticationState(accountName:password:). - Switch on
AuthenticationState. - Complete any required federated authentication or second-factor challenge.
- Call
validateSession()later to check whether stored cookies are still valid. - Call
signout()to clear the client's cookies.
Federated accounts return .waitingForFederatedAuthentication. Open FederationResponse.idpURL
in a browser, let the user finish identity-provider sign-in, then pass the final callback URL to
validateFederatedCallbackURL(_:) or validateFederatedCallbackURLString(_:).
If the user already has a fastlane session, load its cookies into a session and pass that session
to Client:
let loader = FastlaneSessionLoader() let session = try loader.session( fastlaneUser: "[email protected]", environmentValue: { ProcessInfo.processInfo.environment[$0] } ) let client = Client(urlSession: session) let state = try await client.validateSession()
Use FastlaneSessionLoader.Constants.fastlaneSessionEnvVarName as fastlaneUser to load cookies
from FASTLANE_SESSION instead of ~/.fastlane/spaceship/<apple-id>/cookie.
AppleSessionService wraps the low-level client with dependency-injected environment lookup,
keychain storage, prompts, browser opening, validation, login, and logout. It is a good fit for
command-line tools that want a "login if needed" workflow while still controlling where credentials
are stored and how users are prompted.
Add the XcodesLoginKitSecurityKey product when you need FIDO2 security-key support:
.product(name: "XcodesLoginKitSecurityKey", package: "XcodesLoginKit")
When the login state is .waitingForSecondFactor(.securityKey, authOptions, sessionData), call
submitSecurityKeyPinCode(_:sessionData:authOptions:).