WebApi ASP.NET Identity Facebook для входа в систему - программирование
Подтвердить что ты не робот

WebApi ASP.NET Identity Facebook для входа в систему

В потоке аутентификации facebook для идентификации asp.net диалог facebook oauth добавляет код, а не токен доступа в redirect_url, чтобы сервер мог обменять этот код на токен доступа через http://localhost:49164/signin-facebook?code=...&state=....

Моя проблема в том, что мой клиент - мобильное приложение, которое использует sdk facebook, и сразу же дает мне токен доступа. Facebook говорит, что использование sdk всегда дает вам токен доступа, поэтому я могу сразу показать web api токен доступа?

Я понимаю, что это не очень безопасно, но возможно ли это?

4b9b3361

Ответ 1

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

Очевидно, что все параметры OAPA для WebAPI Owin основаны на браузере, то есть они требуют большого количества запросов на перенаправление браузеров, которые не подходят для родного мобильного приложения (в моем случае). Я все еще изучаю и экспериментирую, но как кратко описал Hongye Sun в одном из комментариев к его сообщению в блоге, http://blogs.msdn.com/b/webdev/archive/2013/09/20/understanding-security-features-in-spa-template.aspx?PageIndex=2#comments, чтобы войти с Facebook в токен доступа полученные с помощью Facebook SDK, могут быть проверены непосредственно API, создающим вызов графика на конечную точку /me.

Используя информацию, возвращаемую вызовом графа, вы можете проверить, зарегистрирован ли пользователь или нет. В конце нам нужно подписать пользователя, возможно, используя метод Authentication.SignIn Owin, возвращая токен-носитель, который будет использоваться для всех последующих вызовов API.

EDIT: На самом деле я понял, что маркер-носитель выдается при вызове конечной точки "/Token", которая на входе принимает что-то вроде grant_type=password&username=Alice&password=password123 Проблема здесь в том, что у нас нет пароля (что все указывает на механизм OAuth), так как же еще можно вызвать конечную точку "/Token"?

UPDATE: Я наконец нашел рабочее решение, и вот что мне пришлось добавить к существующим классам, чтобы заставить его работать: Startup.Auth.cs

public partial class Startup
{
    /// <summary>
    /// This part has been added to have an API endpoint to authenticate users that accept a Facebook access token
    /// </summary>
    static Startup()
    {
        PublicClientId = "self";

        //UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
        UserManagerFactory = () => 
        {
            var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
            userManager.UserValidator = new UserValidator<ApplicationUser>(userManager) { AllowOnlyAlphanumericUserNames = false };
            return userManager;
        };

        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            AllowInsecureHttp = true
        };

        OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
        OAuthBearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat;
        OAuthBearerOptions.AccessTokenProvider = OAuthOptions.AccessTokenProvider;
        OAuthBearerOptions.AuthenticationMode = OAuthOptions.AuthenticationMode;
        OAuthBearerOptions.AuthenticationType = OAuthOptions.AuthenticationType;
        OAuthBearerOptions.Description = OAuthOptions.Description;
        OAuthBearerOptions.Provider = new CustomBearerAuthenticationProvider();            
        OAuthBearerOptions.SystemClock = OAuthOptions.SystemClock;
    }

    public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }

    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static Func<UserManager<ApplicationUser>> UserManagerFactory { get; set; }

    public static string PublicClientId { get; private set; }

    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        [Initial boilerplate code]

        OAuthBearerAuthenticationExtensions.UseOAuthBearerAuthentication(app, OAuthBearerOptions);

        [More boilerplate code]
    }
}

public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
    public override Task ValidateIdentity(OAuthValidateIdentityContext context)
    {
        var claims = context.Ticket.Identity.Claims;
        if (claims.Count() == 0 || claims.Any(claim => claim.Issuer != "Facebook" && claim.Issuer != "LOCAL_AUTHORITY" ))
            context.Rejected();
        return Task.FromResult<object>(null);
    }
}

В AccountController я добавил следующее действие

        [HttpPost]
        [AllowAnonymous]
        [Route("FacebookLogin")]
        public async Task<IHttpActionResult> FacebookLogin(string token)
        {
            [Code to validate input...]
            var tokenExpirationTimeSpan = TimeSpan.FromDays(14);            
            ApplicationUser user = null;    
            // Get the fb access token and make a graph call to the /me endpoint    
            // Check if the user is already registered
            // If yes retrieve the user 
            // If not, register it  
            // Finally sign-in the user: this is the key part of the code that creates the bearer token and authenticate the user
            var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
            identity.AddClaim(new Claim(ClaimTypes.Name, user.Id, null, "Facebook"));
                // This claim is used to correctly populate user id
                identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id, null, "LOCAL_AUTHORITY"));
            AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());            
            var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
            ticket.Properties.IssuedUtc = currentUtc;
            ticket.Properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);            
            var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket); 
            Authentication.SignIn(identity);

            // Create the response
            JObject blob = new JObject(
                new JProperty("userName", user.UserName),
                new JProperty("access_token", accesstoken),
                new JProperty("token_type", "bearer"),
                new JProperty("expires_in", tokenExpirationTimeSpan.TotalSeconds.ToString()),
                new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
                new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
            );
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(blob);
            // Return OK
            return Ok(blob);
        }

Что это. Единственное различие, которое я обнаружил с ответом на конечную точку классического /Token, заключается в том, что токен-носитель немного короче, а даты истечения и выпуска указаны в формате UTC, а не в GMT (по крайней мере, на моей машине).

Надеюсь, это поможет!

Ответ 2

Следуя отличному решению от @s0nica, я модифицировал некоторые коды, чтобы интегрироваться с существующим в настоящее время шаблоном ASP.NET MVC. s0nica подходит, но не полностью совместим с MVC (Non-WebApi) AccountController.

Преимущество моего подхода - работать как с ASP.NET MVC, так и с WebApi наоборот.

Основные отличия - это название заявки. В качестве имени заявки FacebookAccessToken используется следующая ссылка (http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx), мой подход совместим с подходом к данной ссылке. Я рекомендую использовать его.

Обратите внимание, что ниже коды являются модифицированной версией ответа @s0nica. Итак, (1) прохождение данной ссылки, (2), а затем прохождение кода s0nica, (3) и, наконец, рассмотрим мое потом.

Файл Startup.Auth.cs.

public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
    {
        // This validates the identity based on the issuer of the claim.
        // The issuer is set in the API endpoint that logs the user in
        public override Task ValidateIdentity(OAuthValidateIdentityContext context)
        {
            var claims = context.Ticket.Identity.Claims;
            if (!claims.Any() || claims.Any(claim => claim.Type != "FacebookAccessToken")) // modify claim name
                context.Rejected();
            return Task.FromResult<object>(null);
        }
    }

апи /AccountController.cs

        // POST api/Account/FacebookLogin
    [HttpPost]
    [AllowAnonymous]
    [Route("FacebookLogin")]
    public async Task<IHttpActionResult> FacebookLogin([FromBody] FacebookLoginModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (string.IsNullOrEmpty(model.token))
        {
            return BadRequest("No access token");
        }

        var tokenExpirationTimeSpan = TimeSpan.FromDays(300);
        ApplicationUser user = null;
        string username;
        // Get the fb access token and make a graph call to the /me endpoint
        var fbUser = await VerifyFacebookAccessToken(model.token);
        if (fbUser == null)
        {
            return BadRequest("Invalid OAuth access token");
        }

        UserLoginInfo loginInfo = new UserLoginInfo("Facebook", model.userid);
        user = await UserManager.FindAsync(loginInfo);

        // If user not found, register him with username.
        if (user == null)
        {
            if (String.IsNullOrEmpty(model.username))
                return BadRequest("unregistered user");

            user = new ApplicationUser { UserName = model.username };

            var result = await UserManager.CreateAsync(user);
            if (result.Succeeded)
            {
                result = await UserManager.AddLoginAsync(user.Id, loginInfo);
                username = model.username;
                if (!result.Succeeded)
                    return BadRequest("cannot add facebook login");
            }
            else
            {
                return BadRequest("cannot create user");
            }
        }
        else
        {
            // existed user.
            username = user.UserName;
        }

        // common process: Facebook claims update, Login token generation
        user = await UserManager.FindByNameAsync(username);

        // Optional: make email address confirmed when user is logged in from Facebook.
        user.Email = fbUser.email;
        user.EmailConfirmed = true;
        await UserManager.UpdateAsync(user);

        // Sign-in the user using the OWIN flow
        var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);

        var claims = await UserManager.GetClaimsAsync(user.Id);
        var newClaim = new Claim("FacebookAccessToken", model.token); // For compatibility with ASP.NET MVC AccountController
        var oldClaim = claims.FirstOrDefault(c => c.Type.Equals("FacebookAccessToken"));
        if (oldClaim == null)
        {
            var claimResult = await UserManager.AddClaimAsync(user.Id, newClaim);
            if (!claimResult.Succeeded)
                return BadRequest("cannot add claims");
        }
        else
        {
            await UserManager.RemoveClaimAsync(user.Id, oldClaim);
            await UserManager.AddClaimAsync(user.Id, newClaim);
        }

        AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
        var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
        properties.IssuedUtc = currentUtc;
        properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);
        AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
        var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
        Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accesstoken);
        Authentication.SignIn(identity);

        // Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
        JObject blob = new JObject(
            new JProperty("userName", user.UserName),
            new JProperty("access_token", accesstoken),
            new JProperty("token_type", "bearer"),
            new JProperty("expires_in", tokenExpirationTimeSpan.TotalSeconds.ToString()),
            new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
            new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString()),
            new JProperty("model.token", model.token),
        );
        // Return OK
        return Ok(blob);
    }

Модель входа в Facebook для привязки (внутренний класс api/AccountController.cs)

    public class FacebookLoginModel
    {
        public string token { get; set; }
        public string username { get; set; }
        public string userid { get; set; }
    }

    public class FacebookUserViewModel
    {
        public string id { get; set; }
        public string first_name { get; set; }
        public string last_name { get; set; }
        public string username { get; set; }
        public string email { get; set; }
    }

Метод VerifyFacebookAccessToken (в api/AccountController.cs)

    private async Task<FacebookUserViewModel> VerifyFacebookAccessToken(string accessToken)
    {
        FacebookUserViewModel fbUser = null;
        var path = "https://graph.facebook.com/me?access_token=" + accessToken;
        var client = new HttpClient();
        var uri = new Uri(path);
        var response = await client.GetAsync(uri);
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            fbUser = Newtonsoft.Json.JsonConvert.DeserializeObject<FacebookUserViewModel>(content);
        }
        return fbUser;
    }

Ответ 3

Да, вы можете использовать маркер внешнего доступа для безопасного входа в систему.

Я настоятельно рекомендую вам следовать этому руководству, в котором показано, как выполнить проверку подлинности на токенах с помощью Web API 2 с нуля (используя Angular JS как front- конец). В частности, шаг 4 включает в себя два метода, которые позволяют выполнять аутентификацию с использованием токена внешнего доступа, например. как возвращено из родного SDK:

[AllowAnonymous, HttpGet]
async Task<IHttpActionResult> ObtainLocalAccessToken(string provider, string externalAccessToken)

[AllowAnonymous, HttpPost]
async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)

В двух словах:

  • Используйте собственный SDK для получения токена внешнего доступа.

  • Вызовите ObtainLocalAccessToken("Facebook", "[fb-access-token]"), чтобы определить, имеет ли пользователь уже учетную запись (ответ 200), и в этом случае для вас будет создан новый локальный токен. Он также проверяет, что токен внешнего доступа является законным.

  • Если вызов на шаге 2 не выполнен (ответ 400), вам необходимо зарегистрировать новую учетную запись, вызвав RegisterExternal, передав внешний токен. У этого урока есть хороший пример (см. associateController.js).