Использование OpenID и OAuth в ASP.NET Identity

134

Одним из преимуществ системы разрешений платформы ASP.NET Identity является то, что информация о пользователях может поступать из внешних источников, даже та информация, которая используется для их идентификации. Это означает, что другие системы могут проверять подлинность пользователей от имени приложения и ASP.NET Identity использует эту идею для создания легкой системы аутентификации на третьей стороне (Third-Party Authentication), через такие сервисы, как Google, Microsoft, Facebook, Twitter, ВКонтакте и т.д. Вы наверняка видели подобные системы аутентификации на многих сайтах, где не нужно проходить процесс регистрации, а достаточно войти в приложение через свой профиль в социальной сети.

Есть множество преимуществ такой системы аутентификации: многие пользователи уже имеют профили в различных социальных сетях, пользователи могут использовать двухфакторную аутентификацию и вы не должны управлять учетными записями пользователя в приложении.

В современных приложениях, такие системы аутентификации реализуются на основе протоколов OpenID и OAuth, описывающих взаимодействие приложения со сторонней системой. Ниже кратко описаны оба этих протокола и приведены схемы их работы:

OpenID

Этот протокол используется для аутентификации пользователей через другие интернет-ресурсы и проверяет, действительно ли пользователь тот — за кого себя выдает. На рисунке ниже приведена схема работы OpenID:

Схема работы OpenID

В двух словах, OpenID позволяет входить на множество сайтов используя один аккаунт провайдера, например профиль в социальной сети Facebook или Google+. Например, когда пользователь заходит на ваш сайт, как показано на схеме выше, ему предоставляется выбор провайдера, после чего он перенаправляется на сайт этого провайдера и вводит свои учетные данные. Если он успешно проходит аутентификацию у провайдера, провайдер возвращает вашему сайту успешный OpenID пользователя и вы уже можете его аутентифицировать в своем приложении.

OAuth

Этот протокол используется для авторизации пользователей через другие интернет-ресурсы и, в отличие от OpenID, позволяет предоставить права на использование какого-то ресурса (например, загрузить аватарку пользователя, его email, поменять его стену Вконтакте или использовать другие API-интерфейсы, доступные у провайдера). На рисунке ниже показана схема работы OAuth:

Схема работы OAuth

После того, как пользователь прошел аутентификацию в другом сервисе, для него генерируется уникальный токен. Благодаря этому токену вы можете загрузить данные пользователя из этого сервиса, либо выполнить другие манипуляции в аккаунте пользователя (список разрешений, которые требует ваше приложение, отображается при аутентификации).

Пример использования аутентификации Google

ASP.NET Identity имеет встроенную поддержку для работы с протоколами OpenID и OAuth для таких сервисов, как Google, Microsoft, Facebook и Twitter, а также содержит более обобщенную поддержку OAuth для интеграции с любыми другими сервисами. В этой статье в качестве примера мы рассмотрим аутентифкацию через учетную запись в социальной сети Google Plus.

Создание приложения в Google Developer

Для начала работы нам необходимо зайти в консоль разработчика Google и создать новый проект, который будет использоваться для авторизации пользователей:

Создание нового проекта в консоли разработчика Google

После создания нового проекта вас перенаправит на его начальную страницу, где нужно будет перейти в пункт меню «Учетные данные», после чего перейти во вкладку «Окно запроса доступа Oauth». В этой вкладке указывается ваш email-адрес, название приложения и его логотип (эта информация будет отображаться пользователю при аутентификации на стороне Google).

Настройки приложения OAuth

Нажмите кнопку «Сохранить». Теперь перейдите в левом меню в раздел «Библиотека» и найдите Google+ API, перейдите в меню этого API:

Подключение API-интерфейса Google+

В открывшемся окне необходимо щелкнуть по кнопке «Включить», как показано на рисунке ниже:

Включение API-интерфейса Google+

После этого отобразится следующее сообщение:

Кнопка создания учетных данных

Вам необходимо нажать на кнопку «Создать учетные данные», для настройки доступа OAuth. После этого откроется окно добавления учетных данных, состоящее из трех этапов. На первом этапе указывается тип API (в проекте может использоваться несколько API) и контекст его использования. В нашем примере мы используем Google+ API и вызываем его из веб-браузера (JavaScript):

Добавление данных об используемом API и контексте его использования

На втором этапе указываются ограничения по адресу сайта, из которого будет вызываться Google+ API:

Ограничение URL на доступ к приложению OAuth

Обратите внимание, что я указал ссылки на localhost:4989, в вашем приложении порт будет другим. Также, при развертывании приложения, необходимо будет добавить еще URL-адрес реального веб-сайта. На третьем этапе отобразиться сгенерированный ключ:

Созданные учетные данные приложения OAuth

Этот ключ является открытым (ClientId). Когда вы нажмете на кнопку «Готово», браузер перенаправит вас на страницу идентификатора клиента в веб-приложении, где отображен также закрытый ключ (ClientSecret). Скопируйте куда-нибудь оба этих ключа, т.к. позже они нам понадобятся.

Сгенерированные открытый и закрытый ключи

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

После того, как вы настроили проект OAuth на сайте Google Developer, можно приступить к интеграции сторонней системы аутентификации в своем приложении. Для начала необходимо установить пакет NuGet, добавляющий библиотеки для работы с аутентифкацей Google через Oauth, используя Identity. Выполните следующую команду в окне Package Manager Console:

Install-Package Microsoft.Owin.Security.Google -version 3.0.1

Есть также еще несколько пакетов NuGet для работы с другими популярными социальными сетями. Они перечислены в таблице ниже:

Пакеты аутентификации NuGet
Пакет Описание
Microsoft.Owin.Security.Google

Проверяет подлинность пользователей через учетную запись Google

Microsoft.Owin.Security.Facebook

Аутентификация пользователей через аккаунт Facebook

Microsoft.Owin.Security.Twitter

Аутентификация пользователей через аккаунт Twitter

Microsoft.Owin.Security.MicrosoftAccount

Аутентификация пользователей через аккаунт Microsoft

Microsoft.Owin.Security.OAuth

Аутентифкация через любые сервисы, поддерживающие OAuth 2.0

После установки пакета, необходимо включить поддержку аутентификации через стороннее приложение в классе запуска OWIN. Для этого отредактируйте файл IdentityConfig.cs в папке App_Start нашего приложения, как показано в примере ниже:

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using Owin;
using System;
using Users.Infrastructure;

namespace Users
{
    public class IdentityConfig
    {
        public void Configuration(IAppBuilder app)
        {
            app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);
            app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);

            app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
            });

            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
            app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
                {
                    AuthenticationType = "Google",
                    ClientId = "879075641925-ne4oeb5uoji2q5ofcpva5vfnlravp3e6.apps.googleusercontent.com",
                    ClientSecret = "cQicsLYWtYJEE09EdQbxWBQk",
                    Caption = "Авторизация через Google+",
                    CallbackPath = new PathString("/GoogleLoginCallback"),
                    AuthenticationMode = AuthenticationMode.Passive,
                    SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(),
                    BackchannelTimeout = TimeSpan.FromSeconds(60),
                    BackchannelHttpHandler = new System.Net.Http.WebRequestHandler(),
                    BackchannelCertificateValidator = null,
                    Provider = new GoogleOAuth2AuthenticationProvider()
                }
            );
        }
    }
}

Каждый из пакетов, которые были перечислены в таблице выше, содержит расширяющий метод, который включает функциональность аутентификации через соответствующий сервис в OWIN. При аутентификации через Google используется метод UseGoogleAuthentication, которому передается объект GoogleOAuth2AuthenticationOptions. В свойствах этого объекта передаются параметры аутентификации. В частности в свойствах ClientId и ClientSecret указываются открытый и закрытый ключи приложения, которые мы сгенерировали ранее.

В свойстве AuthenticationType указывается уникальный идентификатор для экземпляра поставщика аутентификации. Этот идентификатор будет отображаться при запросе свойства Issuer класса утверждения Claim, который мы подробно рассмотрели в предыдущей статье. Благодаря этому параметру можно использовать несколько разных аутентификаций Google, например, используя разные проекты в Google Developer.

Свойство CallbackPath указывает ссылку, на которую будет возвращен пользователь после успешной аутентификации в Google. Важно, чтобы это свойство совпадало с URL перенаправления, который мы указали на втором этапе добавления учетных данных.

Теперь давайте добавим кнопку аутентификации через Google в представление Login.cshtml, находящееся в папке /Views/Account:

@model AuthUsers.Models.LoginViewModel
@{
    ViewBag.Title = "Авторизация на сайте";
}

<h2>Войти</h2>

@Html.ValidationSummary()
@using (Html.BeginForm()) {
    ...
}

<br />

@using (Html.BeginForm("GoogleLogin", "Account", FormMethod.Post, new { style = "margin-top:12px" }))
{
    <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" />
    <button class="btn btn-primary" type="submit">
        Войти с помощью аккаунта Google
    </button>
}

Новая кнопка отправляет данные формы методу действия GoogleLogin контроллера Account. Ниже я показал, какие изменения необходимо внести в этот контроллер:

// ...

namespace AuthUsers.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        // ...

        [HttpPost]
        [AllowAnonymous]
        public ActionResult GoogleLogin(string returnUrl)
        {
            var properties = new AuthenticationProperties
            {
                RedirectUri = Url.Action("GoogleLoginCallback",
                    new { returnUrl = returnUrl })
            };

            HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");
            return new HttpUnauthorizedResult();
        }

        [AllowAnonymous]
        public async Task<ActionResult> GoogleLoginCallback(string returnUrl)
        {
            ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync();
            AppUser user = await UserManager.FindAsync(loginInfo.Login);

            if (user == null)
            {
                user = new AppUser
                {
                    Email = loginInfo.Email,
                    UserName = loginInfo.DefaultUserName,
                    City = Cities.LONDON,
                    Country = Countries.ENG
                };

                IdentityResult result = await UserManager.CreateAsync(user);
                if (!result.Succeeded)
                {
                    return View("Error", result.Errors);
                }
                else
                {
                    result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
                    if (!result.Succeeded)
                    {
                        return View("Error", result.Errors);
                    }
                }
            }

            ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,
                DefaultAuthenticationTypes.ApplicationCookie);

            ident.AddClaims(loginInfo.ExternalIdentity.Claims);

            AuthManager.SignIn(new AuthenticationProperties
            {
                IsPersistent = false
            }, ident);

            return Redirect(returnUrl ?? "/");
        }
    }
}

Метод GoogleLogin создает экземпляр класса AuthenticationProperties и устанавливает свойство RedirectUri с адресом перенаправления, который вызывает метод действия GoogleLoginCallback в этом же контроллере. Следующая часть кода магическим образом вызывает ASP.NET Identity, чтобы перенаправлять пользователя на страницу аутентификации Google, а не ту, которая определена в приложении:

// ...
   
HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");
return new HttpUnauthorizedResult();
            
// ...

Это означает, что когда пользователь нажимает кнопку «Войти с помощью аккаунта Google», его браузер будет перенаправлен на страницу аутентификации Google, а затем обратно перенаправлен на страницу GoogleLoginCallback после того, как он пройдет проверку достоверности.

В методе действия GoogleLoginCallback мы получаем данные о пользователе от Google, используя метод GetExternalLoginInfoAsync, вызываемый на реализации интерфейса IAuthenticationManager:

// ...
ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync();
// ...

Класс ExternalLoginInfo сожержит следующие свойства:

Свойства класса ExternalLoginInfo
Свойство Описание
DefaultUserName

Возвращает имя пользователя

Email

Возвращает адрес электронной почты пользователя

ExternalIdentity

Возвращает объект ClaimsIdentity, связанный с идентификатором пользователям

Login

Возвращает объект UserLoginInfo, который описывает данные при внешнем входе в приложением

Мы используем метод FindAsync класса управления пользователями, для поиска на основе свойства ExternalLoginInfo.Login. Этот метод возвращает объект AppUser, если пользователь ранее уже прошел проверку достоверности в приложении:

// ...
AppUser user = await UserManager.FindAsync(loginInfo.Login);
// ...

Если метод FindAsync не возвращает объект AppUser — это означает, что пользователь впервые аутентифицируется в приложении, поэтому мы создаем новый объект AppUser с данными, полученными от Google и сохраняем нового пользователя в базе данных.

// ...
result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
// ...

Все что остается для аутентификации — добавить утверждения (claims), ассоциированные с данным пользователем и предоставляемые Google, а также создать аутентифицирующий файл cookie, позволяющий использовать учетные данные в приложении между сессиями:

// ...
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,
    DefaultAuthenticationTypes.ApplicationCookie);
ident.AddClaims(loginInfo.ExternalIdentity.Claims);
// ...

Тестирование Google-аутентификации

Перед тестированием аутентификации через Google необходимо внести еще одно изменение в приложение: нам нужно отключить пользовательскую проверку пароля, которую мы включили в статье «Валидация данных». Для этого закомментируйте следующие строки в файле класса AppUserManager:

// ...

namespace Users.Infrastructure
{
    public class AppUserManager : UserManager<AppUser>
    {
        public AppUserManager(IUserStore<AppUser> store)
            : base(store)
        { }

        public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options,
            IOwinContext context)
        {
            AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
            AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));

            /*manager.PasswordValidator = new CustomPasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = true
            };*/

            manager.UserValidator = new CustomUserValidator();

            return manager;
        }
    }
}

Проверку данных пользователя необходимо оставить, т.к. по умолчанию, в ASP.NET Identity в логине можно указывать только буквы английского алфавита. Напомню, ранее мы добавили класс CustomUserValidator, где отключили эту проверку:

// ...
   
namespace Users.Infrastructure
{
    public class CustomUserValidator : IIdentityValidator<AppUser>
    {
        public async Task<IdentityResult> ValidateAsync(AppUser item)
        {
            List<string> errors = new List<string>();

            if (String.IsNullOrEmpty(item.UserName.Trim()))
                errors.Add("Вы указали пустое имя.");

            string userNamePattern = @"^[a-zA-Z0-9а-яА-Я]+$";

            if (!Regex.IsMatch(item.UserName, userNamePattern))
                errors.Add("В имени разрешается указывать буквы английского или русского языков, и цифры");

            if (errors.Count > 0)
                return IdentityResult.Failed(errors.ToArray());

            return IdentityResult.Success;
        }
    }
}

Запустите приложение и нажмите кнопку «Войти с помощью аккаунта Google». Браузер откроет вам страницу Google, где необходимо ввести свои учетные данные:

Ввод учетных данных Google

Когда вы завершите процесс аутентификации, браузер перенаправит вас обратно в приложение. Если теперь открыть страницу с данными по утверждениям /Claims/Index, вы сможете увидеть, как утверждения из системы Google были добавлены к личности пользователя Identity:

Список утверждений на основе учетной записи Google
Пройди тесты
Лучший чат для C# программистов