Подтвердить что ты не робот

Социальная аутентификация ASP.NET Web API для Интернета и мобильных устройств

Мой вопрос довольно сложный, так голый со мной, как я стараюсь его хорошо раскрыть, с чем я борюсь.

Цель

У вас есть веб-сайт ASP.NET, который позволяет пользователям регистрироваться и регистрироваться через Username/Password или Social (Facebook, Twitter, Google и т.д.), также имеющий API. Этот API необходимо заблокировать с помощью [Authorize]. API должен иметь доступ к мобильным клиентам (Android, iOS и т.д.), Которые могут быть подписаны через Username/Password или Social (Facebook, Twitter, Google и т.д.).

Фон

Итак, я сделал сайты, которые могут сделать одну или две вещи из моей цели, но не все вместе. Есть отличные примеры онлайн и встроенные примеры в проекты VS, которые показывают, как позволить пользователю зарегистрироваться и войти в систему через социальные приложения, но они предназначены только для веб-сайта, а не для мобильных устройств. Я сделал веб-сайт, в котором приложение Android использует Username/Password для аутентификации с помощью этого API, но ничего с OAuth или социальными учетными данными.

Я начал использовать эту страницу в качестве ссылки, но я не знаю, как это сделать и заставить ее работать мой сайт регистрируется и регистрируется мое мобильное приложение.

Этот парень заставляет его звучать так просто, но не показывает никакого кода для этого.

Вопрос

Есть ли учебник или пример GitHub где-нибудь, что может привести меня к моей цели? В основном я хочу, чтобы сайт, где люди могли зарегистрировать имя пользователя/пароль или использовать свою социальную учетную запись, а также позволить пользователю делать то же самое (зарегистрироваться и войти в систему) с помощью мобильного устройства. Мобильное устройство будет в основном использовать API для push/pull данных, но я не уверен, как включить социальные логины с моим API. Я предполагаю, что мне нужно использовать OAuth и идти по этому маршруту, но я не могу найти никаких хороших примеров, которые показывают, как это сделать как для Интернета, так и для мобильных устройств.

Или, может быть, это правильное решение состоит в том, чтобы веб-страница была всем файлом cookie, а API - отдельным "веб-сайтом", и все они были аутентифицированы и оба они привязывались к одной и той же базе данных?

4b9b3361

Ответ 1

Я успешно выполнил эту самую задачу в своем собственном приложении ASP.NET MVC с использованием ASP.NET Identity, но затем ударил проблему, о которой вы говорите: мне нужно, чтобы это работало с использованием Web API, чтобы мое мобильное приложение могло взаимодействовать изначально.

Я был незнаком со статьей, которую вы связали, но, прочитав ее, я заметил, что большая часть работы и кода их не является необходимой и усложняет функциональность, которая уже существует в Identity ASP.NET.

Вот мои рекомендации, и я предполагаю, что вы используете ASP.NET Identity V2, который эквивалентен пакетам, окружающим MVC5 (а не новый MVC6 vNext). Это позволит вашему сайту и мобильному приложению через API аутентифицировать как локальный логин (имя пользователя/пароль), так и внешний поставщик OAuth как из веб-представлений MVC на вашем веб-сайте, так и через вызовы веб-API из вашего мобильного приложения:

Шаг 1. При создании проекта убедитесь, что у вас есть оба пакета для MVC и веб-API. В диалоговом окне "Выбор проекта ASP.NET" вы можете выбрать флажки, обеспечить проверку MVC и веб-API. Если вы еще не сделали этого при создании своего проекта, я бы рекомендовал создать новый проект и перенести существующий код по сравнению с поиском и вручную добавить зависимости и код шаблона.

Шаг 2. Внутри файла Startup.Auth.cs вам понадобится код, чтобы сообщить OWIN использовать аутентификацию cookie, разрешить внешний знак в файлах cookie и поддерживать токены на предъявителя OAuth (так будут проверяться вызовы Web API). Это релевантные выдержки из моей рабочей кодовой базы проекта:

Startup.Auth.cs

// Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/account/login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

// Configure the application for OAuth based flow
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AuthorizeEndpointPath = new PathString("/api/account/externallogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            //AllowInsecureHttp = false
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);

 app.UseTwitterAuthentication(
            consumerKey: "Twitter API Key",
            consumerSecret: "Twitter API Secret");

        app.UseFacebookAuthentication(
            appId: "Facebook AppId",
            appSecret: "Facebook AppSecret");

В приведенном выше коде я в настоящее время поддерживаю Twitter и Facebook как поставщиков внешней аутентификации; однако вы можете добавить дополнительных внешних поставщиков с помощью вызовов app.UserXYZProvider и дополнительных библиотек, и они будут подключаться и воспроизводиться с кодом, который я предоставляю здесь.

Шаг 3. Внутри вашего файла WebApiConfig.cs вы должны настроить HttpConfiguration для подтверждения аутентификации хоста по умолчанию и поддержки токенов на предъявителя OAuth. Чтобы объяснить это, это говорит о том, что ваше приложение различает типы аутентификации между MVC и веб-API, таким образом вы можете использовать типичный поток cookie для веб-сайта, в то время как ваше приложение будет принимать токены-носители в форме OAuth из веб-API без жалоб или других вопросы.

WebApiConfig.cs

// Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

Шаг 4. Вам нужен AccountController (или эквивалентно назначенный контроллер) для MVC и Web API. В моем проекте у меня есть два файла AccountController, один контроллер MVC, наследующий от базового класса Controller, и другой AccountController, наследующий от ApiController, который находится в пространстве имен Controllers.API, чтобы все было в порядке. Я использую стандартный шаблон AccountController из проектов Web API и MVC. Вот версия API контроллера учетных записей:

AccountController.cs(пространство имен Controllers.API)

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;

using Disco.Models.API;
using Disco.Providers;
using Disco.Results;

using Schloss.AspNet.Identity.Neo4j;
using Disco.Results.API;

namespace Disco.Controllers.API
{
    [Authorize]
    [RoutePrefix("api/account")]
    public class AccountController : ApiController
    {
        private const string LocalLoginProvider = "Local";
        private ApplicationUserManager _userManager;

        public AccountController()
        {            
        }

        public AccountController(ApplicationUserManager userManager,
            ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
        {
            UserManager = userManager;
            AccessTokenFormat = accessTokenFormat;
        }

        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }

        // GET account/UserInfo
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("userinfo")]
        public UserInfoViewModel GetUserInfo()
        {
            ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

            return new UserInfoViewModel
            {
                Email = User.Identity.GetUserName(),
                HasRegistered = externalLogin == null,
                LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
            };
        }

        // POST account/Logout
        [Route("logout")]
        public IHttpActionResult Logout()
        {
            Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
            return Ok();
        }

        // GET account/ManageInfo?returnUrl=%2F&generateState=true
        [Route("manageinfo")]
        public async Task<ManageInfoViewModel> GetManageInfo(string returnUrl, bool generateState = false)
        {
            IdentityUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());

            if (user == null)
            {
                return null;
            }

            List<UserLoginInfoViewModel> logins = new List<UserLoginInfoViewModel>();

            foreach (UserLoginInfo linkedAccount in await UserManager.GetLoginsAsync(User.Identity.GetUserId()))
            {
                logins.Add(new UserLoginInfoViewModel
                {
                    LoginProvider = linkedAccount.LoginProvider,
                    ProviderKey = linkedAccount.ProviderKey
                });
            }

            if (user.PasswordHash != null)
            {
                logins.Add(new UserLoginInfoViewModel
                {
                    LoginProvider = LocalLoginProvider,
                    ProviderKey = user.UserName,
                });
            }

            return new ManageInfoViewModel
            {
                LocalLoginProvider = LocalLoginProvider,
                Email = user.UserName,
                Logins = logins,
                ExternalLoginProviders = GetExternalLogins(returnUrl, generateState)
            };
        }

        // POST account/ChangePassword
        [Route("changepassword")]
        public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword,
                model.NewPassword);

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // POST account/SetPassword
        [Route("setpassword")]
        public async Task<IHttpActionResult> SetPassword(SetPasswordBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // POST account/AddExternalLogin
        [Route("addexternallogin")]
        public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);

            AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);

            if (ticket == null || ticket.Identity == null || (ticket.Properties != null
                && ticket.Properties.ExpiresUtc.HasValue
                && ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
            {
                return BadRequest("External login failure.");
            }

            ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);

            if (externalData == null)
            {
                return BadRequest("The external login is already associated with an account.");
            }

            IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
                new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // POST account/RemoveLogin
        [Route("removelogin")]
        public async Task<IHttpActionResult> RemoveLogin(RemoveLoginBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityResult result;

            if (model.LoginProvider == LocalLoginProvider)
            {
                result = await UserManager.RemovePasswordAsync(User.Identity.GetUserId());
            }
            else
            {
                result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(),
                    new UserLoginInfo(model.LoginProvider, model.ProviderKey));
            }

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // GET account/ExternalLogin
        [OverrideAuthentication]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
        [AllowAnonymous]
        [Route("externallogin", Name = "ExternalLoginAPI")]
        public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
        {
            if (error != null)
            {
                return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
            }

            if (!User.Identity.IsAuthenticated)
            {
                return new ChallengeResult(provider, this);
            }

            ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

            if (externalLogin == null)
            {
                return InternalServerError();
            }

            if (externalLogin.LoginProvider != provider)
            {
                Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
                return new ChallengeResult(provider, this);
            }

            ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
                externalLogin.ProviderKey));

            bool hasRegistered = user != null;

            if (hasRegistered)
            {
                Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);

                 ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
                    OAuthDefaults.AuthenticationType);
                ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
                    CookieAuthenticationDefaults.AuthenticationType);

                AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
                Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
            }
            else
            {
                IEnumerable<Claim> claims = externalLogin.GetClaims();
                ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
                Authentication.SignIn(identity);
            }

            return Ok();
        }

        // GET account/ExternalLogins?returnUrl=%2F&generateState=true
        [AllowAnonymous]
        [Route("externallogins")]
        public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
        {
            IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
            List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();

            string state;

            if (generateState)
            {
                const int strengthInBits = 256;
                state = RandomOAuthStateGenerator.Generate(strengthInBits);
            }
            else
            {
                state = null;
            }

            foreach (AuthenticationDescription description in descriptions)
            {
                ExternalLoginViewModel login = new ExternalLoginViewModel
                {
                    Name = description.Caption,
                    Url = Url.Route("ExternalLogin", new
                    {
                        provider = description.AuthenticationType,
                        response_type = "token",
                        client_id = Startup.PublicClientId,
                        redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
                        state = state
                    }),
                    State = state
                };
                logins.Add(login);
            }

            return logins;
        }

        // POST account/Register
        [AllowAnonymous]
        [Route("register")]
        public async Task<IHttpActionResult> Register(RegisterBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };

            IdentityResult result = await UserManager.CreateAsync(user, model.Password);

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

        // POST account/RegisterExternal
        [OverrideAuthentication]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("registerexternal")]
        public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var info = await Authentication.GetExternalLoginInfoAsync();
            if (info == null)
            {
                return InternalServerError();
            }

            var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };

            IdentityResult result = await UserManager.CreateAsync(user);
            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            result = await UserManager.AddLoginAsync(user.Id, info.Login);
            if (!result.Succeeded)
            {
                return GetErrorResult(result); 
            }
            return Ok();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && _userManager != null)
            {
                _userManager.Dispose();
                _userManager = null;
            }

            base.Dispose(disposing);
        }

        #region Helpers

        private IAuthenticationManager Authentication
        {
            get { return Request.GetOwinContext().Authentication; }
        }

        private IHttpActionResult GetErrorResult(IdentityResult result)
        {
            if (result == null)
            {
                return InternalServerError();
            }

            if (!result.Succeeded)
            {
                if (result.Errors != null)
                {
                    foreach (string error in result.Errors)
                    {
                        ModelState.AddModelError("", error);
                    }
                }

                if (ModelState.IsValid)
                {
                    // No ModelState errors are available to send, so just return an empty BadRequest.
                    return BadRequest();
                }

                return BadRequest(ModelState);
            }

            return null;
        }

        private class ExternalLoginData
        {
            public string LoginProvider { get; set; }
            public string ProviderKey { get; set; }
            public string UserName { get; set; }

            public IList<Claim> GetClaims()
            {
                IList<Claim> claims = new List<Claim>();
                claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));

                if (UserName != null)
                {
                    claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
                }

                return claims;
            }

            public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
            {
                if (identity == null)
                {
                    return null;
                }

                Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);

                if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
                    || String.IsNullOrEmpty(providerKeyClaim.Value))
                {
                    return null;
                }

                if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
                {
                    return null;
                }

                return new ExternalLoginData
                {
                    LoginProvider = providerKeyClaim.Issuer,
                    ProviderKey = providerKeyClaim.Value,
                    UserName = identity.FindFirstValue(ClaimTypes.Name)
                };
            }
        }

        private static class RandomOAuthStateGenerator
        {
            private static RandomNumberGenerator _random = new RNGCryptoServiceProvider();

            public static string Generate(int strengthInBits)
            {
                const int bitsPerByte = 8;

                if (strengthInBits % bitsPerByte != 0)
                {
                    throw new ArgumentException("strengthInBits must be evenly divisible by 8.", "strengthInBits");
                }

                int strengthInBytes = strengthInBits / bitsPerByte;

                byte[] data = new byte[strengthInBytes];
                _random.GetBytes(data);
                return HttpServerUtility.UrlTokenEncode(data);
            }
        }

        #endregion
    }
}

Шаг 5. Вам также необходимо создать ApplicationOAuthProvider, чтобы сервер мог генерировать и проверять токены OAuth. Это предусмотрено в примере проекта WebAPI. Это моя версия файла:

ApplicationOAuthProvider.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;
using Butler.Models;

using Schloss.AspNet.Identity.Neo4j;

namespace Butler.Providers
{
    public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
    {
        private readonly string _publicClientId;

        public ApplicationOAuthProvider(string publicClientId)
        {
            if (publicClientId == null)
            {
                throw new ArgumentNullException("publicClientId");
            }

            _publicClientId = publicClientId;
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

            ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);

            if (user == null)
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }

            ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
               OAuthDefaults.AuthenticationType);
            ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
                CookieAuthenticationDefaults.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(user.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);
        }

        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            // Resource owner password credentials does not provide a client ID.
            if (context.ClientId == null)
            {
                context.Validated();
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == _publicClientId)
            {
                //Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                //if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                //{
                    context.Validated();
                //}
            }

            return Task.FromResult<object>(null);
        }

        public static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
            {
                { "userName", userName }
            };
            return new AuthenticationProperties(data);
        }
    }
}

Также включен ChallengeResult, который будет использовать интерфейс Web API вашего приложения, чтобы справляться с задачами, предоставляемыми внешними поставщиками логина, для аутентификации вашего пользователя:

ChallengeResult.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;

namespace Butler.Results
{
    public class ChallengeResult : IHttpActionResult
    {
        public ChallengeResult(string loginProvider, ApiController controller)
        {
            LoginProvider = loginProvider;
            Request = controller.Request;
        }

        public string LoginProvider { get; set; }
        public HttpRequestMessage Request { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            Request.GetOwinContext().Authentication.Challenge(LoginProvider);

            HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

С помощью этого набора кода вы сможете HTTP GET и HTTP POST маршрутов в версии API AccountController для регистрации пользователя, входа в систему с использованием имени пользователя и пароля для получения токена-носителя, добавления/удаления внешних логинов, управлять внешними входами и, что самое важное, для вашей проблемы, аутентифицировать, передав внешний маркер входа в обмен на токен-маркер OAuth для вашего приложения.

Ответ 2

Вы можете посмотреть эту серию статей, чтобы узнать, покрывает ли она вашу цель:

Аутентификация на основе токена с использованием ASP.NET Web API 2, Owin и Identity Taiseer Joudeh (который также часто отвечает на вопросы о SO)

Статьи посвящены созданию службы аутентификации на основе токенов с использованием OWIN, а одна из частей - с использованием внешних логинов (например, Facebook и Google+). Примеры в основном сосредоточены вокруг веб-приложения как потребителя веб-службы, но он также должен работать и на мобильных приложениях. В статьях есть связанный с проектом проект GitHub, а также очень активная секция комментариев, где почти никто не отвечает на все вопросы.

Надеюсь, это может привести вас к вашей цели.

Ответ 3

Я добавляю это как отдельный ответ на вторую часть вашего вопроса, чтобы сказать, что ДА вы можете иметь два отдельных проекта, привязанных к одной базе данных, и просто иметь проект веб-сайта MVC/Web Forms используйте всю аутентификацию cookie, а затем создайте отдельный проект веб-API, который является аутентификацией всех токенов.

В моем более длинном ответе с примерами исходного кода то, что я в основном сделал, объединяет два отдельных проекта в один проект, чтобы избежать избыточного кода модели и кода контроллера. В моем случае это имело для меня больше смысла; однако я склонен сказать, что индивидуальные предпочтения и потребности вашего проекта диктуют, нужно ли поддерживать два отдельных проекта, один веб-сайт и одну конечную точку веб-API или объединить их.

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