Поток кода авторизации с Identitity4 и OidcClient

Для настольного приложения Winforms я буду использовать поток кода авторизации с PKCE. В качестве поставщика удостоверений я использую IdentityServer, а в качестве клиентской библиотеки - OicdClient. Следующим шагом я должен решить, какой браузер использовать для входа пользователя:

SystemBrowser говорит о простой / понятной реализации потока. Для Extended WebBrowser говорится, что у какого-то пользователя может не быть SystemBrowser. Но WebBrowser - это более старая версия IE? и можно ли его использовать для безопасной аутентификации?

Тем не менее я попробовал образец "Extended WebBrowser" и не смог интегрировать его в свою среду прототипа с собственным сервером IS4. Поэтому мне нужна некоторая ясность с потоком кода и перенаправлением. Я уже реализовал этот поток кода авторизации с чистыми классами.Net, но использование OicdClient немного сбивает меня с толку (вначале как черный ящик).

Мой вопрос: как перенаправление работает с этими библиотеками, и кто отвечает за перенаправление и кто отвечает за получение перенаправления с кодом (для обмена на токен доступа)?

Поток кода состоит из следующих шагов (без таких деталей, как clientID, PKCE ...):

  1. Отправить запрос кода в IS4
  2. Ответ IS4 со страницей входа (отображается в браузере)
  3. После успешного входа в систему IS4 отправляет URL-адрес перенаправления с кодом
  4. HttpListener получает это перенаправление с кодом и
  5. Отправьте запрос в IS4 с кодом для получения токена доступа

С OidcClient и в автоматическом режиме:

var options = new OidcClientOptions
{
    Authority = "https://demo.identityserver.io",
    ClientId = "native",
    RedirectUri = redirectUri,
    Scope = "openid profile api",
    Browser = new SystemBrowser()
};

var client = new OidcClient(options);
var result = await client.LoginAsync();

Для меня здесь много волшебства. Только вызов LoginAsync() заставляет его работать...

Важным моментом, по-видимому, является свойство Browser опций с интерфейсом IBrowser и его реализация этого метода:

    public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken)
    {
            using (var listener = new LoopbackHttpListener(Port, _path))
            {
                OpenBrowser(options.StartUrl);
                try
                {
                    var result = await listener.WaitForCallbackAsync();
                    if (String.IsNullOrWhiteSpace(result))
                    {
                        return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Empty response." };
                    }
                    return new BrowserResult { Response = result, ResultType = BrowserResultType.Success };
                }
                catch (TaskCanceledException ex)
                { ....}
            }
        }

если я попытаюсь сопоставить шаги потока:

  1. Страница входа: OpenBrowser(options.StartUrl);
  2. Редирект будет делать IS4? SystemBrowser из образца этого не делает.
  3. Получите код: await listener.WaitForCallbackAsync();

1 и 5, вероятно, выполняются OicdClient. Этот пример довольно ясен, необходимо подтвердить, что перенаправление выполняется IS4.

Реализация в другом примере Extended WebBrowser

public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var form = _formFactory.Invoke())
            using (var browser = new ExtendedWebBrowser()
            {
                Dock = DockStyle.Fill
            })
            {
                var signal = new SemaphoreSlim(0, 1);

                var result = new BrowserResult
                {
                    ResultType = BrowserResultType.UserCancel
                };

                form.FormClosed += (o, e) =>
                {
                    signal.Release();
                };

                browser.NavigateError += (o, e) =>
                {
                    e.Cancel = true;

                    if (e.Url.StartsWith(options.EndUrl))
                    {
                        result.ResultType = BrowserResultType.Success;
                        result.Response = e.Url;
                    }
                    else
                    {
                        result.ResultType = BrowserResultType.HttpError;
                        result.Error = e.StatusCode.ToString();
                    }

                    signal.Release();
                };

                browser.BeforeNavigate2 += (o, e) =>
                {
                    var b = e.Url.StartsWith(options.EndUrl);
                    if (b)
                    {
                        e.Cancel = true;
                        result.ResultType = BrowserResultType.Success;
                        
                        result.Response = e.Url;
                        
                        signal.Release();
                    }
                };

                form.Controls.Add(browser);
                browser.Show();

                System.Threading.Timer timer = null;

                form.Show();
                browser.Navigate(options.StartUrl);

                await signal.WaitAsync();
                if (timer != null) timer.Change(Timeout.Infinite, Timeout.Infinite);

                form.Hide();
                browser.Hide();

                return result;
            }
        }
  1. Сделано: browser.Navigate(options.StartUrl);
  2. Перенаправление через IS4
  3. Получение кода в обработчике события: NavigateError???

Здесь что-то не так? На IS4 вызывается AccountController.Login, который вызывает / соединяет / авторизует / выполняет обратный вызов? с помощью redirect_uri. Но это не касается BeforeNavigate2. вместо этого появляется событие NavigateError, где установлен результат:

result.ResultType = BrowserResultType.Success;
result.Response = e.Url; 

1 ответ

Решение

В настоящее время рекомендуется использовать веб-браузер пользователя по умолчанию, а не встраивать компонент браузера. Что касается того, как это реализовать - поскольку вы не можете перехватывать события навигации браузера с помощью этого подхода, вам необходимо реализовать HTTP-прослушиватель, который может принимать запрос POST от вашегоidentityserver4 реализация.

Прочтите это: https://auth0.com/blog/oauth-2-best-practices-for-native-apps/

И этот RFC: https://tools.ietf.org/html/rfc8252

Другие вопросы по тегам