Трябва ли да приемем влизане в Apple за използване от страна на сървъра?

[Юли 2020 г.: Вижте също Apple Sign-In: персонализирани сървъри и загадка с изтичане (част 2)]

Е, постигам добър напредък в интегрирането на влизане в Apple с моето приложение, но не беше лесно. И все още имам въпрос - който изглежда като спирачка.

Целта ми да напиша тази статия е да споделя наученото от мен и да се надявам да получа обратна връзка. Някак си се чувствам като в пустинята, въпреки че вярвам, че моят случай на употреба не е толкова необичаен.

Искам да използвам OAuth токените, които са резултат от влизане в Apple, за достъп до HTTP REST API на моя сървър.

Първо, позволете ми да опиша по-подробно моята система от гледна точка на влизане. Моята система може би е малко необичайна, тъй като няма естествен тип за влизане. Той позволява влизане с различни социални входове и доставчици на облачни услуги: Dropbox, Google и Facebook.

Наскоро интегрирах Microsoft (OneDrive) от страна на сървъра и работя върху влизането в Apple. Фокусът всъщност е върху доставчиците на облачни услуги. Моето приложение (Neebla в Apple App Store и SyncServerII в задната част) има концепция BYOCS — „Внесете свое собствено облачно хранилище“.

Данните, качени от потребителите, обикновено се съхраняват в тяхната собствена система за съхранение в облак и тези данни могат безопасно да се споделят с други потребители. Социалните влизания (включително влизане в Apple) се извършват само чрез покана от потребител с облачно хранилище — и хранилището на данните на социалния потребител се делегира към хранилището на канещия потребител.

Пример за типично влизане и използване на REST API на Neebla и SyncServerII е влизане в Google. Google предоставя клиентска рамка за iOS. Потребителят влиза със своето потребителско име и идентификационни данни в Google и няколко неща произтичат от това:

  1. OAuth токени — код за оторизация и токени за достъп.
  2. Токенът за достъп се обновява автоматично от страната на клиента от рамката за влизане.
  3. Тези токени могат да бъдат изпратени до моя персонализиран сървър и токенът за достъп се валидира лесно с помощта на крайни точки на Google.
  4. Първоначалната проверка на токена на персонализирания сървър може да се извърши в началото на обработката на заявката за крайна точка, без достъп до базата данни на персонализирания сървър (както в „стила тук“).

Споменавам тези точки, за да създам рамка от очаквания за това, което бих искал да видя в други доставчици на вход (не защото съм заклет фен на Google).

Сега ще премина към влизането в Apple и ще опиша как работи този процес от гледна точка на внедряване. Ще разгледам това в три стъпки, които съответстват на механизмите в моята система.

1. Влизане от страна на клиента

Моят опит с интегрирането на Apple Sign In в моето клиентско приложение за iOS беше до голяма степен както е описано във видеото за разработчици на Apple.

С няколко изключения. „Използвах тяхното демо приложение“. Двата проблема, които срещнах, изглежда са свързани със симулатора на iOS, поне от Xcode 11.1.

Акаунтът не се запазва от стартиране до стартиране на приложението (напр. с помощта на техния getCredentialState метод) и приложението се заключва доста лесно.

Първият проблем е най-вече раздразнение. Просто трябва да продължите да влизате. За да коригирам втория проблем, или превключвам към друг симулатор на устройство, или излизам от моя акаунт в Apple в приложението за настройки.

В момента използвам резултатите от влизането като вход за моето по-нататъшно развитие. По-конкретно, използвам петте елемента, получени по-долу.

Ще се спра на три от тях. Свойството userпредоставя уникален ключ за идентифициране на потребителя, който да се използва вместо имейл адреса. authorizationCodeи identityTokenса за OAuth.

authorizationCode може да бъде предаден на вашия сървър, за да създаде токен за опресняване. Повече за това следва в част 3 по-долу. identityToken (или само ID токен) е JWT (JSON уеб токен), който изтича след 10 минути. Можете също да прехвърлите това на вашия сървър.

Този знак е източник на непрекъснато разочарование за мен, така че ще кажа още няколко неща.

ID токенът несе обновява в приложението за iOS. Например вижте тази тема във форума за разработчици на Apple. Това противоречи на типичните ми очаквания за механизъм за влизане в OAuth.

Освен това неизглежда възможно да се създаде друг ID токен от страната на сървъра, който удължава датата на изтичане на ID токена. По-конкретно, маркерът за опресняване (отново, вижте 3 по-долу) може да се използва за валидиране, а не за създаване на нов ID маркер. Вижте документите на Apple.

Оказва се, че ID токенът се създава и предоставя на вашето приложение само когато потребителят влезе. Така че очаквайте да го получите само веднъж.

Това стартира главоблъсканицата с персонализирания сървър REST API.

Какво трябва да се изпрати на персонализиран сървър, когато приложение за iOS използва влизане в Apple за удостоверяване със сървъра?

В референтния случай, който обмислям, влизане в Google, изпращам токена за достъп OAuth. Неговата дата на изтичане се актуализира периодично, тъй като рамката за влизане в Google актуализира периодично токена за достъп в приложението за iOS.

Изглежда има два токена, които можем да използваме за изпращане от клиентското iOS приложение към сървъра:

  1. Идентификационният токен.
  2. Токенът за опресняване (съжалявам, продължете да четете).

„Очертах някои съображения с тези стратегии тук“. Едно допълнително съображение с маркера за опресняване е, че някои хора смятат, че не трябва да се дава на клиенти - например, MSAL за iOS не позволява достъп на клиента на iOS до маркер за опресняване.

Като цяло токените за опресняване са мощни и основното правило е, че на клиентите не трябва да се вярва.

Поради тези причини избрах да изпратя ID токена на моя сървър за удостоверяване на крайна точка. Ще поддържам този токен в Keychain на моя клиент. Също така изпращам кода за оторизация на моя сървър, но не запазвам това на клиента (напр. никога не се изпраща обратно на клиента).

2. Първоначална проверка на идентификационните данни от страна на сървъра

Моят персонализиран сървър, написан на Swift, използва IBM’s Kitura Credential framework от плъгини, за да извърши първоначално оторизиране за всяка заявка за крайна точка.

„Например за Google“, това проверява дали потребителят (представен чрез токен за достъп OAuth) е потребител на Google. Той непроверява дали потребителят има акаунт в системата.

Създавам „Apple плъгин Kitura Credentials за влизане“. Този плъгин използва ID токена, за да извърши първоначално упълномощаване за потребител. Той непроверява изтичането на ID токена — поради посочените по-горе причини.

Вместо това се предполага, че ще се извърши допълнително валидиране на сървъра (вижте раздел 3 по-долу).

Проверката на валидността на ID токена не е съвсем лесна. Apple не говори за това в двата видеоклипа на WWDC 2019, които видях. И техните онлайн документи са оскъдни.

Няколко ранни осиновители обаче са писали в блогове за това и това помага. Например вижте Къртис Хърбърт и Окта.

Кодът за моя приставка за идентификационни данни за Kitura за влизане в Apple е може би най-доброто, което мога да предложа за проверка на валидността на ID токен, но ще дам кратко описание на стъпките, които трябва да следвате, за да обобщя:

  1. Вземете JSON уеб ключа на Apple (JWK) чрез HTTP заявка за крайна точка до Apple.
  2. Реконституирайте този JWK в PEM публичен ключ.
  3. Проверете подписа на ID токена, като използвате този публичен ключ (не забравяйте, ID токенът е JWT).
  4. Декодирайте полезния товар на ID токена.
  5. Валидирайте претенциите на полезния товар, с изключение на датата на изтичане.

Използвам две рамки, за да направя тежката работа за горните стъпки:

Оставям ви да се разровите в моя код за подробности. Давам и още няколко уеб връзки там.

Още един детайл, който ще дам тук. Тези стъпки непроверяват с Apple, за да видите дали приложението ви в момента е оторизирано от този потребител. По-конкретно, единственото HTTP/мрежово извикване по-горе е в стъпка 1, което е просто HTTP GET, при което не предоставяте информация на Apple.

3. Крайна проверка от страна на сървъра

Добре, все още четеш. Остава още една „стъпка“. И това е глупост. съжалявам

Какво остава? Е, направихме първоначална проверка от страна на сървъра на потребителя в стъпка 2 по-горе. Въпреки това всъщност не сме проверили с Apple дали този потребител е валиден в момента. (И трябва да проверите дали потребителят всъщност е потребител във вашата система, но това е извън обхвата на този блог).

Отново, Apple не предоставя видеоклипове и оскъдна документация за този най-сложен аспект на влизането в Apple, ако извършвате окончателна проверка за персонализиран сървър.

Цялостната стратегия, която използвам, за да направя тази окончателна проверка на идентификационните данни, е периодично да използвам маркера за опресняване, за да направя валидиране.

Това предава информация за потребителя и приложението към крайна точка на Apple в HTTP POST повикване и предполагам, че ще се провали, ако потребителят е отменил разрешенията за приложението. Периодично, правя това не повече от веднъж на всеки 24 часа — „тъй като Apple заявява, че може да ви задуши, ако го правите повече от това често“.

Токенът за опресняване се създава чрез кода за оторизация и малко работа от ваша страна. В моята система кодът за оторизация се предава на моя сървър от приложението за iOS, когато потребителят създаде акаунт.

Оказва се, че програмният код за (а) създаване на токена за опресняване и (б) използване на токена за опресняване за извършване на валидиране е много подобен. По-долу ще дам основните стъпки.

Отново ще ви насоча към моя програмен код за пълните подробности за това как да обменяте код за оторизация за токен за опресняване. Този път вижте моето SyncServerII repo и конкретно файловете AppleSignInCreds+Refresh.swift и AppleSignInCreds+ClientSecret.swift.

Има набор от параметри, от които ще се нуждаете, за да създадете токена за опресняване и да потвърдите токена за опресняване. Това е структурата на моя сървър, която съдържа тези параметри:

Долните четири (keyId, teamId, privateKey и clientId) са основните параметри, от които се нуждаете, за да генерирате клиентската тайна. В други системи (напр. влизане в Google) клиентската тайна за OAuth е просто низ, който копирате и поставяте.

За влизане в Apple клиентската тайна е по-сложна. Всъщност това е JWT, който трябва да създадете и подпишете.

teamId идва от уебсайта на Apple Developer (вижте следващата фигура). clientId е вашият идентификатор на пакет или идентификатор на приложение за вашето приложение за iOS, напр. biz.SpasticMuffin.SharedImages.

За да получите keyId и privateKey, трябва да преминете през стъпките на уебсайта за разработчици на Apple. Ето URL, за да направите това в момента и изглежда така:

Това е и начинът, по който изтегляте личния ключ. Пазете личния ключ на лично място! За моя сървър това влиза в частен конфигурационен файл на JSON сървър.

След като имате тези параметри, трябва да ги използвате в JWT заглавка и полезен товар и да го подпишете, за да създадете JWT низ. За подробности вижте файла, който цитирам по-горе — AppleSignInCreds+ClientSecret.swift. Отново използвам Swift JWT за тежко вдигане.

Сега имате JWT низа, т.е. тайната на клиента. Нуждаете се също от URI за пренасочване. За това изглежда имате нужда от собствен защитен с TLS (HTTPS) уеб домейн. Yark. Казах ти, че тази последна стъпка е глупава.

Вие настройвате това URI за пренасочване, като използвате Идентификационен номер на услугатав уеб сайта на разработчиците на Apple. И когато отидете там и докоснете голямата икона +, изглежда така:

Намерих тази стъпка объркваща. Не само, че трябва да поставите уеб домейн (напр. yourdomain.com) и „Върнат URL“ (напр. webddomain.com/callback) — и все още не съм сигурен за какво се използват — и да качите специален файл на специално място на вашия уеб сървър, вие също трябва да дадете „Идентификатор“:

Което има някои лошо посочени ограничения/свойства. Доколкото мога да преценя, той не можеда бъде същият като идентификатора на вашето приложение за iOS. И така, използвах biz.SpasticMuffin.SharedImages.AppleSignIn, който изглежда работи досега.

Не забравяйте, че основната точка на това упражнение за идентификатор на услуги е да получите URL адрес за пренасочване, който ви е необходим, за да създадете маркера за опресняване. Помните ли маркера за опресняване?

Като се има предвид всичко по-горе, трябва да сте в състояние да съберете всичко заедно и да създадете маркера за опресняване от кода за оторизация. Вижте метода generateRefreshToken във файла, който цитирам по-горе — AppleSignInCreds+Refresh.swift.

Дълбок дъх. Всичко върви добре, сега имате жетон за опресняване. В моя сървър запазвам това в потребителски запис в базата данни на моя сървър и по-късно, не повече от всеки 24 часа, правя стъпка за валидиране на токена за опресняване.

Тази стъпка за валидиране е много подобна на процеса по-горе за генериране на маркера за опресняване. Вижте моя метод validateRefreshToken в AppleSignInCreds+Refresh.swift.

В заключение

Е, или сте прочели всичко по-горе, или просто сте го прегледали до края. Във всеки случай не е много красиво. От петте системи за влизане тип OAuth, които съм включил в моя сървър, тази беше най-голямата трудност. И това, от което съм най-малко доволен.

Защо не съм доволен? Нека прегледаме, използвайки моята начална точка на рамката за влизане в Google.

1. OAuth токени — код за оторизация и токени за достъп

Получаваме и двете с влизане в Apple. (И аз размивам разграничението, наричайки ID токен токен за достъп, което не е съвсем правилно.)

2. Означението за достъп се опреснява автоматично от страната на клиента от рамката за влизане

Ние неразбираме това. И всъщност очевидно не можем да получим актуализиран ID токен.

Това е най-големият ми проблем. Нямам наистина добро първоначално, за HTTP заявка, средство за валидиране на потребител в REST API на моя сървър — защото изглежда трябва да игнорирам датата на изтичане на ID токена.

3. Тези токени могат да бъдат изпратени до моя персонализиран сървър, а токенът за достъп се потвърждава лесно — чрез крайни точки на Google

Тук изтъкнах трудността. Препоръчваните от Apple средства за валидиране на ID токен не използват крайни точки на Apple. И те не говорят за проблеми с изтичането на токена.

4. Първоначална проверка на токен

Първоначалната проверка на токена на персонализирания сървър може да се извърши в началото на обработката на заявката за крайна точка — без достъп до базата данни на персонализираните сървъри (както в стила в Kitura Credentials).

Може би достатъчно казано. Не съм доволен от първоначалната проверка на токена.

Чудя се дали има уязвимост в сигурността, при която нападател по някакъв начин получава ID токена за потребител. Сега те могат да получат постоянен достъп до сървъра - защото токенът на практика не изтича.

Хммм. Мисля, че трябва да използвам допълнителна стратегия. Част от работата по-горе, за създаване на клиентска тайна, включваше подписване на моя собствен JWT. Какво ще кажете за изпращане обратно на клиента, приблизително на всеки 24 часа, моя собствен JWT с изтичане, което може да се провери? И изхвърляне на неизтичащия идентификатор на Apple след първоначалното взаимодействие със сървъра.

Хммм. Но не. Това изглежда отваря по-голяма кутия с червеи. Нападател, който е получил ID токена, може просто да продължи да получава тези допълнителни или нови JWT и да продължи да има фалшив валиден достъп до сървъра.

Изглежда необходимо клиентът да може да изпраща някои текущи, актуализирани идентификационни данни към сървъра. Тези текущи, актуализирани идентификационни данни могат да ограничат щетите, нанесени от атака, при която някой е получил един токен за идентификация.

Резюме

В обобщение, може да е твърде рано да приемаме влизане в Apple за използване от страна на сървъра.

Тяхната документация за основните части на кодирането от страна на сървъра е оскъдна. И изглежда, че липсва поддръжка за основен случай на използване от страна на сървъра: Този за сигурно удостоверяване към REST API на персонализиран сървър.

Досега не мога да стигна до заключението, че трябва да има начин за получаване на актуализиран ID токен на iOS клиента. Някои насоки по тези въпроси от Apple ще бъдат много оценени.

Актуализация (19.10.19)

Получих отговор от техническата поддръжка на разработчиците на Apple. Попитах:

Проблемът ми е, че не мога да разбера много добър начин за използване на системата за влизане на Apple по този начин [както е описано тук]. Основният ми проблем е, че ID токените, издадени от Apple Sign In, очевидно не могат да бъдат опреснени — и изтичат бързо (след 10 минути).

Apple отговори с:

Прегледах вашата заявка и стигнах до заключението, че няма поддържан начин за постигане на желаната функционалност предвид текущите конфигурации на системата за доставка.

Както допълнително посъветва този човек за поддръжка, ще направя заявка за промяна чрез Apple Feedback Assistant. Освен това, въпреки че все още не съм получил това потвърждение (поисках потвърждение в последващ въпрос), смятам да използвам това като причина все още да не поддържам Apple Sign In в следваща версия на приложението за Neebla.

Допълнителна актуализация (публикувана на 4/4/20)

На 2/2/20 имах допълнителна имейл комуникация с Apple Developer Technical Support (DTS). За съжаление естеството им беше просто да добавят подробности към това, което вече знам. Проблемът, който имам, все още не е решен.

След като човекът от Apple DTS прегледа по-нататъшния си отговор, добавяйки подробности, аз отговорих с това по-долу (добавих удебеляване, което не беше в имейла ми до Apple).

Благодарим ви за имейла и продължаващата поддръжка. Мисля обаче, че все още не общуваме напълно по този въпрос.

Подозирам, че това се свежда до очаквания относно случаите на употреба, които този вид (т.е. Apple Sign In) на системата от тип OAuth трябва да поддържа. Мисля, че Apple Sign In поддържа два основни типа случаи на употреба:

(1) Първоначална връзка от приложение за iOS (и може би други клиентски приложения) към персонализиран сървър, когато потребителят влезе за първи път в клиента за iOS. identityToken може да се използва за това, тъй като не трябва да е изтекъл по време на кратката продължителност на тази първоначална връзка.

(2) Потвърждение най-много веднъж на ден на персонализиран сървър за това дали конкретният потребител все още е валиден потребител („потвърдете, че Apple ID на потребителя на това устройство все още е в добро състояние в Apple сървъри”). Това може да стане с помощта на маркера за опресняване.

Случаят на употреба, който очаквам да присъства и който досега не съм успял да разреша с помощта на Apple Sign In, основно попада между (1) и (2) по-горе по отношение на времето. Този случай на използване е удостоверяване на текущи заявки от клиентското приложение за iOS към персонализирания сървър. Тези заявки попадат в интервала от след като identityToken е изтекъл до 24-часовия знак. Те попадат и в интервала между всеки следващ 24-часов период. Да кажем например, че потребителят оттегля правата (напр. чрез https://appleid.apple.com/account/manage) за достъп до приложението един час след първоначалното изтичане на токена за самоличност (или 25 часа след изтичането му, или 49 часа...). Как персонализираният сървър може да се справи с това? Струва ми се, че оставянето на 24-часови интервали между възможностите за удостоверяване на сървъра е проблем със сигурността. Нападателят може да е получил достъп до токените, използвани за удостоверяване, и да получи до 24 часа достъп до сървъра.

За мен това е типичен основен случай на използване на удостоверяване на заявка на персонализиран сървър и нещо, което мога да правя с други системи от тип OAuth (напр. Google Sign In, Facebook). По-типично очаквам, че токенът за достъп може да бъде обновен от страна на клиента или някакъв друг механизъм е предоставен от страна на сървъра, така че валидността на потребителя да може да се проверява по-често (от веднъж на 24 часа).

Окончателна актуализация (юли 2020 г.)

Apple прави актуализация на своята система за влизане в Apple. Може би са слушали хора като мен? :). Вижте Apple Sign-In: персонализирани сървъри и загадка с изтичане (част 2).