Ответ 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 (по крайней мере, на моей машине).
Надеюсь, это поможет!