Перейти к содержанию

Добавление нового OAuth-провайдера

TESSA поддерживает вход в систему с помощью внешних провайдеров, которые реализуют протокол OAuth 2.0 или OpenID Connect (OIDC) - расширение протокола OAuth.

Чтобы добавить нового провайдера в приложение, достаточно настроить параметры этого провайдера в конфигурационном файле app-oauth.json для веб-сервиса web. Параметры описаны подробно в руководстве по настройке аутентификации через OAuth.

Далее мы рассмотрим добавление OIDC-провайдера на примере GitLab и добавление OAuth-провайдера на примере GitHub.

Провайдер GitLab

Так как GitLab полноценно поддерживает протокол OIDC, то достаточно просто сконфигурировать провайдер и настроить конфигурационный файл app-oauth.json приложения TESSA.

Регистрация приложения у провайдера

Чтобы воспользоваться провайдером GitLab в качестве поставщика услуг для внешней аутентификации необходимо зарегистрировать и настроить приложение, которое будет взаимодействовать с внешним провайдером. В нашем случае таким приложением является TESSA.

Обратитесь к документации для создания приложения GitLab.

После создания и настройки приложения будет сформирован идентификатор (ClientId) и секрет (ClientSecret) клиента. Данные параметры необходимо запомнить, чтобы использовать далее при настройке конфигурации провайдера в приложении TESSA.

Important

При регистрации приложения в качестве пути обратного вызова необходимо использовать конечную точку /oauth/callback.

Настройка провайдера в приложении

Для добавления провайдера в TESSA необходимо модифицировать конфигурационный файл app-oauth.json:

{ "Name": "GitLab", "Icon": "gitlab.png", "Authority": "https://gitlab.com/", "GetClaimsFromUserInfoEndpoint": true, "ResponseType": "code id_token", "UsePkce": true, "UseOpenIdConnect": true, "Claims": { "username": "given_name", "preferred_username": "nickname" }, "ClientId": "****", "ClientSecret": "***" }

где

  • "Icon" - параметр с иконкой провайдера, которая размещается на окне входа в систему. Сохраните иконку провайдера gitlab.png в папку с веб-сервисом web в подпапку /wwwroot/images/oauth.

  • "Authority" - общедоступная конечная точка, которая обслуживает документ конфигурации OIDC для GitLab. Если необходимо использовать протокол OAuth вместо OIDC, то выполните следующие шаги:

    • удалите параметр "UseOpenIdConnect" или установите для него значение false,

    • вместо параметра "Authority" укажите следующие параметры:

      "AuthorizationEndpoint": "https://gitlab.com/oauth/authorize", "TokenEndpoint": "https://gitlab.com/oauth/token", "UserInformationEndpoint": "https://gitlab.com/oauth/userinfo"

    • укажите необходимые области доступа, например:

      "Scopes": [ "openid", "profile", "email" ]

  • "ClientId" - идентификатор клиента, который был получен при регистрации приложения у провайдера.

  • "ClientSecret" - секрет клиента, который был получен при регистрации приложения у провайдера.

Узнать допустимые конечные точки (endpoints), области действия (scopes), утверждения (claims) и другую информацию о конфигурации приложения можно в документации GitLab.

Проверка внешней аутентификации

Запустите веб-сервис web и перейдите на окно входа в систему. Убедитесь, что в окне входа отображается кнопка для входа с помощью GitLab.

Нажмите на кнопку для выполнения внешней аутентификации. Веб-клиент выполнит перенаправление на страницу поставщика услуг:

Если пользователь был ранее зарегистрирован в GitHub, то достаточно ввести логин и пароль пользователя. Иначе - необходимо выполнить регистрацию. После ввода корректного логина и пароля для учётной записи GitLab, будет отображено окно с подтверждением выдачи доступа приложению TESSA.

После подтверждения доступа веб-клиент выполнит перенаправление в TESSA и произведёт вход в систему.

Если же доступ был запрещён, то веб-клиент выполнит перенаправление в TESSA и отобразит сообщение об ошибке.

Note

Если пользователь отсутствовал в системе TESSA на момент аутентификации, то он будет создан, если в конфигурационном файле app-oauth.json включён соответствующий параметр.

Провайдер GitHub

Многие поставщики услуг внешней аутентификации, в силу своей специфики, могут отклоняться от спецификации протоколов OAuth и OIDC, что приводит к изменению набора стандартных параметров в запросе на аутентификацию, а также к изменению способа обработки ответа от внешнего провайдера. Некоторые провайдеры поддерживают OIDC только частично, а некоторые не поддерживают его вовсе. Поэтому невозможно реализовать универсальный обработчик, учитывающий все особенности аутентификации с помощью протоколов OAuth и OIDC.

Для учёта нюансов и особенностей аутентификации с различными провайдерами, необходимо создавать специфические для каждого из них обработчики аутентификации и детально реализовывать в них протоколы OAuth и OIDC (если они поддерживаются). Например, GitHub не поддерживает OIDC, хотя в его конфигурации он упоминается. Этот сервис также отклоняется от спецификации OAuth, и для получения пользовательской информации, такой как адрес электронной почты пользователя, нужно использовать отдельную конечную точку.

Давайте рассмотрим создание схемы внешней аутентификации с использованием протокола OAuth на примере поставщика услуг GitHub.

Регистрация приложения у провайдера

Чтобы воспользоваться провайдером GitHub в качестве поставщика услуг для внешней аутентификации необходимо зарегистрировать и настроить приложение, которое будет взаимодействовать с внешним провайдером. В нашем случае таким приложением является TESSA.

Обратитесь к документации для создания приложения GitHub.

После создания и настройки приложения будет сформирован идентификатор (ClientId) и секрет (ClientSecret) клиента. Данные параметры необходимо запомнить, чтобы использовать далее при настройке конфигурации провайдера в приложении TESSA.

Important

При регистрации приложения в качестве пути обратного вызова необходимо использовать конечную точку /oauth/callback. При необходимости конечную точку можно установить другую, тогда придётся перезаписать свойство RemoteAuthenticationOptions.CallbackPath при добавлении новой схемы внешней аутентификации в методе расширения (см. далее).

Настройка провайдера в приложении

Для добавления провайдера в TESSA необходимо модифицировать конфигурационный файл app-oauth.json:

{ "Name": "GitHub", "Caption": { "en": "Login with GitHub", "ru": "Войти с GitHub" }, "AuthorizationEndpoint": "https://github.com/login/oauth/authorize", "TokenEndpoint": "https://github.com/login/oauth/access_token", "UserInformationEndpoint": "https://api.github.com/user", "UserEmailsEndpoint": "https://api.github.com/user/emails", "EnterpriseDomain": null, "AllowSignup": null, "Scopes": [ "user", "read:user", "user:email" ], "Claims": { "login": "given_name", "avatar_url": "picture" }, "ClientId": "****", "ClientSecret": "****" }

где

  • "ClientId" - идентификатор клиента, который был получен при регистрации приложения у провайдера;

  • "ClientSecret" - секрет клиента, который был получен при регистрации приложения у провайдера.

Узнать допустимые конечные точки (endpoints), области действия (scopes), утверждения (claims) и другую информацию о конфигурации приложения можно в документации GitHub.

Создание схемы внешней аутентификации

В корне проекта Tessa.Extensions.Server.Web добавьте папку OAuth с подпапкой GitHub:

|- Tessa.Extensions.Server.Web |- OAuth |- GitHub

Внутри папки GitHub создайте файл GitHubOptions.cs.

Класс GitHubOptions используется как промежуточное хранилище для сеанса внешней аутентификации. В нём можно разместить дополнительные свойства и методы, которые будут использоваться в обработчике внешней аутентификации.

GitHubOptions.cs

using Microsoft.AspNetCore.Authentication.OAuth;

namespace Tessa.Extensions.Server.Web.OAuth.GitHub { /// <summary> /// Набор опций для конфигурирования GitHub-аутентификации /// посредством OAuth, используемые в <see cref="GitHubHandler"/>. /// </summary> public sealed class GitHubOptions : OAuthOptions { /// <summary> /// Имя домена, используемое при локальном развертывании GitHub Enterprise. /// Значение может быть <see langword="null"/>, если версия Enterprise не используется. /// </summary> public string? EnterpriseDomain { get; set; }

/// <summary> /// Конечная точка для получения адреса электронной почты аутентифицированного пользователя. /// </summary> public string? UserEmailsEndpoint { get; set; }

/// <summary> /// Предоставляется ли пользователям возможность регистрации на GitHub во время процесса OAuth, /// если они не прошли проверку подлинности. Используйте значение <see langword="false"/>, /// когда политика запрещает регистрацию. По умолчанию регистрация разрешена. /// </summary> public bool? AllowSignup { get; set; } } }

Внутри папки GitHub создайте файл GitHubPostOptions.cs.

Класс GitHubPostOptions используется для финальной настройки опций GitHubOptions после их конфигурирования. Внутри объекта GitHubPostOptions можно переопределить свойства опций и задать для них корректное значение.

GitHubPostOptions.cs

using System; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.Extensions.Options;

namespace Tessa.Extensions.Server.Web.OAuth.GitHub { /// <summary> /// Объект, используемый для установки значений по умолчанию для всех <see cref="GitHubOptions"/>. /// </summary> public sealed class GitHubPostOptions : IPostConfigureOptions<GitHubOptions> { #region Constants

/// <summary> /// Путь по умолчанию, используемый для GitHub Enterprise v3 REST API. /// </summary> private const string EnterpriseApiPath = "/api/v3";

/// <summary> /// Путь по умолчанию, используемый для <see cref="OAuthOptions.AuthorizationEndpoint"/>. /// </summary> private const string AuthorizationEndpointPath = "/login/oauth/authorize";

/// <summary> /// Путь по умолчанию, используемый для <see cref="OAuthOptions.TokenEndpoint"/>. /// </summary> private const string TokenEndpointPath = "/login/oauth/access_token";

/// <summary> /// Путь по умолчанию, используемый для <see cref="OAuthOptions.UserInformationEndpoint"/>. /// </summary> private const string UserInformationEndpointPath = "/user";

/// <summary> /// Путь по умолчанию, используемый для <see cref="GitHubOptions.UserEmailsEndpoint"/>. /// </summary> private const string UserEmailsEndpointPath = "/user/emails";

#endregion

#region Base Overrides

/// <inheritdoc/> public void PostConfigure(string? name, GitHubOptions options) { ThrowIfNull(options);

if (!string.IsNullOrWhiteSpace(options.EnterpriseDomain)) { options.AuthorizationEndpoint = CreateUrl(options.EnterpriseDomain, AuthorizationEndpointPath); options.TokenEndpoint = CreateUrl(options.EnterpriseDomain, TokenEndpointPath); options.UserInformationEndpoint = CreateUrl(options.EnterpriseDomain, EnterpriseApiPath + UserInformationEndpointPath); options.UserEmailsEndpoint = CreateUrl(options.EnterpriseDomain, EnterpriseApiPath + UserEmailsEndpointPath); } }

#endregion

#region Private Methods

private static string CreateUrl(string domain, string path) { var builder = new UriBuilder(domain) { Path = path, Port = -1, Scheme = Uri.UriSchemeHttps, };

return builder.Uri.ToString(); }

#endregion } }

Внутри папки GitHub создайте файл GitHubHandler.cs.

Класс GitHubHandler используется для обработки запросов внешней аутентификации. В нём учитываются специфичные параметры провайдера:

  • при создании запроса внешней аутентификации задаётся дополнительный параметр allow_signup в строке запроса, чтобы сообщить поставщику услуг о необходимости отображения регистрационного окна, если пользователь ранее не был зарегистрирован;

  • при получении информации о пользователе используется отдельная конечная точка для получения адреса электронной почты пользователя, к которой выполняется дополнительный запрос с токеном доступа.

GitHubHandler.cs

using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Net.Mime; using System.Security.Claims; using System.Text.Encodings.Web; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Tessa.Web.Client.OAuth;

namespace Tessa.Extensions.Server.Web.OAuth.GitHub { /// <summary> /// Обработчик GitHub-аутентификации посредством OAuth. /// </summary> public sealed class GitHubHandler : OAuthDefaultHandler<GitHubOptions> { #region Constructors

/// <summary> /// Создаёт экземпляр класса <see cref="GitHubHandler"/>. /// </summary> /// <inheritdoc /> public GitHubHandler( IOptionsMonitor<GitHubOptions> options, ILoggerFactory logger, UrlEncoder encoder, IOAuthEventHandler eventHandler) : base(options, logger, encoder, eventHandler) { }

#endregion

#region Base Overrides

/// <inheritdoc /> protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) { // Дополнительную информацию см. https://docs.github.com/ru/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps

// Сначала выполняется базовый метод, чтобы получить сформированный URL с основными параметрами var baseUrl = base.BuildChallengeUrl(properties, redirectUri); var parameters = QueryHelpers.ParseQuery(new Uri(baseUrl).Query);

// Затем в строку запроса добавляются дополнительные параметры this.SetQueryParameter(parameters, properties, OAuthChallengeProperties.ScopeKey, this.FormatScope, this.Options.Scope); this.SetQueryParameter(parameters, properties, "allow_signup", this.FormatBoolean, this.Options.AllowSignup);

// Некоторые свойства удаляются при настройке параметров запроса, поэтому состояние необходимо сбросить parameters["state"] = this.Options.StateDataFormat.Protect(properties);

// Добавление параметров к конечной точке авторизации return QueryHelpers.AddQueryString(this.Options.AuthorizationEndpoint, parameters); }

/// <inheritdoc/> protected override async Task InitializeEventsAsync() { await base.InitializeEventsAsync();

this.Events.OnCreatingTicket = async context => { // Если адрес электронной почты не является публичным, то его необходимо // получить из конечной точки emails, если указана соответствующая область видимости if (context.Identity is { } identity && !string.IsNullOrWhiteSpace(this.Options.UserEmailsEndpoint) && !identity.HasClaim(claim => claim.Type is ClaimTypes.Email) && this.Options.Scope.Any(scope => scope is "user" or "user:email")) { var email = await this.GetEmailAsync(context.TokenResponse, this.Context.RequestAborted); if (!string.IsNullOrWhiteSpace(email)) { identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, this.Options.ClaimsIssuer)); } } }; }

#endregion

#region Private Methods

private async Task<string?> GetEmailAsync(OAuthTokenResponse tokens, CancellationToken cancellationToken) { // Запрос на получение адреса электронной почты пользователя. Подробнее см. https://developer.github.com/v3/users/emails/ using var request = new HttpRequestMessage(HttpMethod.Get, this.Options.UserEmailsEndpoint); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); request.Version = this.Backchannel.DefaultRequestVersion;

using var response = await this.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); if (!response.IsSuccessStatusCode) { this.Logger.LogError( "An error occurred while retrieving the email address associated with the logged in user: the remote server returned a {0} response with the following payload: {1} {2}.", response.StatusCode, response.Headers.ToString(), await response.Content.ReadAsStringAsync(cancellationToken));

throw new HttpRequestException("An error occurred while retrieving the email address associated to the user profile."); }

return (await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: cancellationToken)) .EnumerateArray() .Where(address => address.GetProperty("primary").GetBoolean()) .Select(address => address.GetString("email")) .FirstOrDefault(); }

#endregion } }

Внутри папки GitHub создайте файл GitHubConfigurator.cs.

В классе GitHubConfigurator реализованы методы для добавления схемы аутентификации с помощью внешнего провайдера GitHub и настройки этой аутентификации.

GitHubExtensions.cs

using System.Collections.Generic; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Tessa.Platform.Storage; using Tessa.Web.Client.OAuth;

namespace Tessa.Extensions.Server.Web.OAuth.GitHub { /// <summary> /// Объект, посредством которого выполняется добавление и настройка /// внешней аутентификации по протоколу OAuth для провайдера GitHub. /// </summary> public sealed class GitHubConfigurator : OAuthDefaultConfigurator<GitHubOptions, GitHubHandler> { #region Constructors

/// <summary> /// Создает экземпляр класса <see cref="GitHubConfigurator"/>. /// </summary> /// <inheritdoc/> public GitHubConfigurator(Dictionary<string, object?> provider) : base(provider) { }

#endregion

#region Base Overrides

/// <inheritdoc/> public override void AddScheme(AuthenticationBuilder builder) { builder.Services.TryAddSingleton<IPostConfigureOptions<GitHubOptions>, GitHubPostOptions>();

base.AddScheme(builder); }

/// <inheritdoc/> protected override void ConfigureOptions(GitHubOptions options) { base.ConfigureOptions(options);

options.UserEmailsEndpoint = this.Provider.TryGet<string?>(nameof(GitHubOptions.UserEmailsEndpoint)); options.EnterpriseDomain = this.Provider.TryGet<string?>(nameof(GitHubOptions.EnterpriseDomain)); options.AllowSignup = this.Provider.TryGet<bool?>(nameof(GitHubOptions.AllowSignup)); // options.CallbackPath = "/signin-github"; }

#endregion } }

Внутри папки OAuth создайте файл WebRegistrator.cs.

В классе WebRegistrator выполняется регистрация схемы внешней аутентификации, которая использует специфичный обработчик для GitHub. Регистрация выполняется внутри DI-контейнера ASP NET.

WebRegistrator.cs

using Tessa.Extensions.Server.Web.OAuth.GitHub; using Tessa.Web.Client.OAuth; using Tessa.Web.Registrations;

namespace Tessa.Extensions.Server.Web.OAuth { /// <summary> /// Регистратор, используемый для конфигурации аутентификации посредством OAuth. /// </summary> [WebRegistrator] public sealed class WebRegistrator : WebRegistratorBase { /// <inheritdoc/> public override void InitializeRegistration() { OAuthHandlersRegistry.Instance.TryRegister("GitHub", provider => new GitHubConfigurator(provider)); } } }

Проверка внешней аутентификации

Запустите веб-сервис web и перейдите на окно входа в систему. Убедитесь, что в окне входа отображается кнопка для входа с помощью GitHub.

Нажмите на кнопку для выполнения внешней аутентификации. Веб-клиент выполнит перенаправление на страницу поставщика услуг:

Если пользователь был ранее зарегистрирован в GitHub, то достаточно ввести логин и пароль пользователя. Иначе - необходимо выполнить регистрацию. После ввода корректного логина и пароля для учётной записи GitHub, веб-клиент выполнит перенаправление в TESSA и произведёт вход в систему:

Note

Если пользователь отсутствовал в системе TESSA на момент аутентификации, то он будет создан, если в конфигурационном файле app-oauth.json включён соответствующий параметр.

Back to top