在asp.net身份的facebook身份验证流程中,facebook oauth对话框会将代码而不是访问令牌附加到redirect_url,以便服务器可以通过http://localhost:49164/signin-facebook?code=...&state=...
将此代码交换为访问令牌。
我的问题是我的客户端是一个使用facebook sdk的移动应用程序,它直接给我一个访问令牌。 Facebook说使用sdk总是给你一个访问令牌,所以我可以直接给web api访问令牌吗?
我知道这不是很安全,但它是否可能?
答案 0 :(得分:27)
我不知道你是否终于找到了解决方案,但我正在尝试做一些非常相似的事情,而我仍然把这些难题放在一起。 我试图将此作为评论而不是答案发布,因为我没有提供真正的解决方案,但它太长了。
显然,所有WebAPI Owin OAuth选项都是基于浏览器的,即它们需要大量不适合原生移动应用程序的浏览器重定向请求(我的情况)。 我正在调查和试验,但正如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调用的承载令牌。
编辑:
实际上我弄错了,在调用“/ Token”端点时发出了不记名令牌,在输入上接受类似grant_type=password&username=Alice&password=password123
的内容
这里的问题是我们没有密码(这是OAuth机制的重点),那么我们如何才能调用“/ Token”端点?
更新: 我终于找到了一个有效的解决方案,以下是我必须添加到现有类中以使其工作的内容: 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);
}
就是这样。我发现经典/令牌端点响应的唯一区别是,承载令牌略短,过期和发布日期为UTC,而不是GMT(至少在我的机器上)。
我希望这有帮助!
答案 1 :(得分:16)
在@ s0nica的强大解决方案之后,我修改了一些代码以便与当前实现的ASP.NET MVC模板集成。 s0nica方法很好,但与MVC(非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);
}
}
<强> API / 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;
}
答案 2 :(得分:13)
是的,您可以使用外部访问令牌安全登录。
我强烈建议您关注this tutorial,它将向您展示如何从头开始使用Web API 2进行基于令牌的身份验证(使用Angular JS作为前端)。特别是,step 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)。