Hey all! My team and I have been looking into possible ways to integrate our [future] Discourse instance into our mobile application - Noom. Our iOS app is written in Objective-C with some Swift, and our Android application is written in Java with some Kotlin. We’ve been debating between full integration via API or web view as our goal is an SSO flow where our users can seamlessly transition from an in-app experience to our Discourse instance.
I’m currently waiting on our QA team to get back to me on which authentication protocols we currently use, but I was curious as to anyone else’s experience integrating Discourse into a mobile application with or without SSO, and what methods (if any) you found most useful throughout the process. I’m aware of Discourse’s compatibility with OAuth/2, although not aware of other potential protocols.
I’m going to update the documentation about delegated authentication soon, but I can give you some pointers right here.
First you need to open a browser session to discourse.site/user-api-key/new with the following parameters:
scopes: 'notifications,session_info,one_time_password',
client_id: YOUR_APP_CLIENT_ID,
nonce: GENERATED_NONCE,
auth_redirect: YOUR_APP_URL_SCHEME,
application_name: YOUR_APP_NAME,
push_url: PUSH_URL (if you are going to send PNs from Discourse to your app),
public_key: PUBLIC_KEY (generated in your app)
You can have a look at the implementation of our DiscourseMobile app for details on the above but the main idea is that your app will launch a browser screen to the URL above, asking the user to authenticate to the Discourse site and authorize your app access to it. Once user authorizes access, Discourse will redirect to YOUR_APP_URL_SCHEME?payload= with an encrypted payload. You’ll need to set up your app to decrypt the payload and store the authToken. In iOS, you should use ASWebAuthenticationSession | Apple Developer Documentation (I don’t know if there is an Android equivalent).
Your authToken can make API requests limited by the scopes requested initially, for a full list of scopes, please look under allow user api key scopes in site settings.
The one_time_password scope allows the authToken to make a request for a one-time-password. The endpoint for this is /user-api-key/otp with the parameters auth_redirect, application_name and public_key.
I will write a proper documentation shortly, but this should help you get started.
Я собираюсь взяться за эту задачу в ближайшие несколько дней и сообщу о результатах — удалось ли мне или нет. Если у кого-то есть руководства или советы, буду рад их получить
Привет, Penar, спасибо за советы! Я далеко не эксперт в этом, раньше мало работал с криптографией или подобными API, поэтому хотел бы попросить вашей помощи в том, как кодировать некоторые из этих параметров. Я изучаю ваше приложение DiscourseMobile, но ранее не использовал JavaScript, поэтому у меня возникают некоторые трудности.
nonce и client_id — это просто случайные строки из 16 и 32 байт, закодированные в шестнадцатеричном формате?
public_key — есть ли какие-либо рекомендации по его генерации? Я пытаюсь использовать этот ресурс, что оставляет мне SecKeyRef. Нужно ли мне преобразовать его в NSData, а затем закодировать в UTF-8?
Не случилось ли у вас быть каких-либо фрагментов кода на Swift или Objective-C? Я разберусь сам, но поскольку всё это для меня ново, любая помощь будет очень ценна.
Привет, Penar, спасибо за ссылки, они очень полезны.
Я продвигаюсь, но полагаю, что неправильно форматирую свой открытый ключ. Как выглядит форматирование здесь?
В консоли логов я вижу:
OpenSSL::PKey::RSAError (Ни PUB-ключ, ни PRIV-ключ: вложенная ошибка asn1) /var/www/discourse/app/controllers/user_api_keys_controller.rb:189:in `initialize’
Спасибо! Я наконец-то продвинулся: проблема была в кодировании параметров. Я использовал URLQueryAllowedCharacterSet из iOS, который не кодирует символы “/” и “+”. Как только я создал тестовый скрипт на Ruby, чтобы изолировать строку, на которой происходил сбой, я смог проанализировать ситуацию задним числом. Спасибо!
Не могу заставить перенаправление работать корректно, используя ASWebAuthenticationSession, как предложил Penar. Вызовы, похоже, выполняются как ожидалось: я вижу диалоговое окно «Authorize», и после нажатия на него появляется просто белый экран. Я предполагаю, что это попытка выполнить перенаправление, но оно не закрывается и не передаёт данные в мой обратный вызов.
Я проверил, что мой пользовательский URL работает нормально в Safari. Более того, если я удалю auth_redirect, мне показывается страница «We have just generated a new user API key to use…», так что всё, кроме перенаправления, кажется работающим.
Кроме того, и это, вероятно, связано с предыдущим, я заметил, что если я пытаюсь добавить гиперссылку в обсуждение, используя свой пользовательский URL, она некликабельна. Не упустил ли я какую-то настройку для разрешения пользовательских URL? Любая помощь будет оценена
edit: Конечно, я нашёл это сразу после вопроса. В настройках действительно нужно указать «Allowed user api auth redirects» (Разрешённые перенаправления авторизации API пользователя) с вашей пользовательской схемой.
Пустой белый экран, скорее всего, указывает на то, что сервер столкнулся с ошибкой. При повторной попытке проверьте логи вашего сайта Discourse в /logs — там, вероятно, есть какая-то информация. Скорее всего, вы столкнулись с проблемой, когда один и тот же идентификатор клиента нельзя зарегистрировать несколько раз. Возможно, потребуется вручную очистить этот идентификатор клиента на экземпляре Discourse; на данный момент это можно сделать только через rails console.
Да, это было из настройки «Разрешенные перенаправления аутентификации API пользователя» в панели администратора. Нужно было её настроить, и всё заработало.
В данный момент я могу пройти аутентификацию, но у меня возникают проблемы с декодированием полезной нагрузки на iOS. Я могу взять полученную полезную нагрузку и свой закрытый ключ и декодировать их в Ruby или NodeJS, чтобы доказать, что всё работает, но заставить iOS декодировать это напрямую оказалось для меня сложнее, чем я думал.
Если вы посмотрите исходный код нашего приложения, там есть примеры того, как выполнять декодирование. Чтобы заставить всё это работать, потребовалось довольно много кода.
Спасибо, мне удалось всё настроить. Позже я опубликую фрагменты кода, чтобы помочь тем, кто хочет реализовать это нативно на iOS. Благодарю за рекомендации.
Можно ли использовать API-токен, созданный таким образом, для аутентификации входа в веб-представлении? Я предполагал, что именно так работает приложение, но сейчас я всё ещё работаю над этим аспектом. Моя цель — обеспечить как доступ к API, так и возможность просмотра через обычный веб-клиент Reface с помощью единого входа.