Apple enforcing 2FA for all accounts.
Starting February 2021, additional authentication will be required for all users to sign in to App Store Connect. This extra layer of security for your Apple ID helps ensure that you’re the only person who can access your account. You can enable two-step verification or two-factor authentication now in the Security section of your Apple ID account or in the Apple ID section of Settings on your device.
It prevents some Fastlane automated tasks such as binary uploads without 2FA.
The best solution is to use the App Store Connect API key instead of the username and password authentication.
lane :release do api_key = app_store_connect_api_key( key_id: "D383SF739", issuer_id: "6053b7fe-68a8-4acb-89be-165aa6465141", key_filepath: "./AuthKey_D383SF739.p8", duration: 1200, # optional in_house: false, # optional but may be required if using match/sigh ) pilot(api_key: api_key) end
See also:
However, if you want to automate an operation that is not supported by the App Store Connect API, such as downloading dSYM, or if you are unable to obtain an App Store Connect API key for some reason, I will share a way to automate to prompt the two-factor authentication code.
Set up a 3rd party web service to receive SMS
Vonage SMS API and their Australian phone number is able to receive SMS from Apple. Not many services seem to be able to receive Apple's 2FA SMS. I have tried Twilio and Clickatell, but they cannot receive Apple's SMS.
Sign up for Vonage and go to "Buy Numbers" to get an Australian phone number.
Add the phone number as a trusted phone number for Apple ID two-factor authentication. You can set multiple phone numbers.
Update Fastlane session cookies periodically on CI
When using Fastlane with an Apple ID that has two-factor authentication enabled, you will be asked to enter the two-factor authentication number when App Store Connect requires login.
In an interactive environment, you can enter the code, but on a CI systems, you cannot enter it and you will be stuck there. To solve this problem, you can generate a session cookie for successful login in advance and reuse it.
You can generate a session cookie with the fastlane spaceauth
command and save the session variable or session cookie files to the CI cache for reuse.
See also:
The following code automates 2FA authentication and updating Fastlane login sessions. You can run this code periodically on CI.
The generated session cookies are saved in the ~/.fastlane
directory, so the ~/.fastlane
directory is saved in the cache of CI and restored in each job of CI.
require "net/http" require "uri" require "pty" require "expect" require "fastlane" require "spaceship" require_relative "./setup_credentials" def fastlane_spaceauth(user, password, default_phone_number) ENV["FASTLANE_USER"] = user ENV["FASTLANE_PASSWORD"] = password # SMS will be sent to the number specified # by `SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER` environment variable. ENV["SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER"] = default_phone_number $expect_verbose = true # Run the `fastlane spaceauth` command using PTY to respond to 2FA input cmd = "fastlane spaceauth" PTY.spawn(cmd) do |i, o| o.sync = true # If there is a valid session cookie, do nothing i.expect(/Pass the following via the FASTLANE_SESSION environment variable:/, 10) do |match| if match o.puts "y" return end end # If the session is invalid and need to enter the 2FA code i.expect(/Please enter the 6 digit code you received at .+:/, 60) do |match| raise "UnknownError" unless match sleep 10 now = Time.now.utc - 120 date_start = now.strftime("%Y-%m-%dT%H:%M:%SZ") date_end = (now + 120 + 120).strftime("%Y-%m-%dT%H:%M:%SZ") api_key = ENV["VONAGE_API_KEY"] api_secret = ENV["VONAGE_API_SECRET"] # Retrieve SMS containing 2FA code from the API uri = URI.parse("https://api.nexmo.com/v2/reports/records?account_id=#{api_key}&product=SMS&direction=inbound&include_message=true&date_start=#{date_start}&date_end=#{date_end}") request = Net::HTTP::Get.new(uri) request.basic_auth(api_key, api_secret) options = { use_ssl: true, } response = Net::HTTP.start(uri.hostname, uri.port, options) do |http| http.request(request) end records = JSON.parse(response.body)["records"] if records.nil? || records.empty? raise "NotFoundError" end message_body = records[0]["message_body"] # Parse a 2FA code from the SMS body code = message_body[/\d{6}/] if code.nil? || code.empty? raise "NotFoundError" end # Enter the code o.puts code end i.expect(/Pass the following via the FASTLANE_SESSION environment variable:/, 10) do |match| raise "UnknownError" unless match o.puts "y" end begin while (i.eof? == false) puts i.gets end rescue Errno::EIO end end end
Another solution: Forwarding SMS using an Android device
On Android, you can get SMS from the program. So it may be possible to achieve this more cheaply by Android device with SIM and setting up a program to forward SMS to Gmail, etc.
See the following example: