新的MVC5项目只用Web API.添加了Facebook AppId和Secret.
通过传入UserName和Password,我可以从Token端点获取我的Web API的Token.然后使用该令牌进行进一步的呼叫.
但
我想在iOS应用程序的Facebook SDK的帮助下注册新用户.
我正在使用Facebook SDK获取访问令牌. (假设在这一点上,我有一个访问令牌).
接下来我知道的是通过将该令牌传递给具有承载[访问令牌]的授权头部,但是会导致500个服务器错误,从而调用api / Account / RegisterExternal端点.
我想我知道的原因,Cookie缺失.我用Fidler的一个cookie做了同样的电话,它的工作. (通过转到ExternalLogins端点提供的URL接收Cookie).
由于cookie缺少等待Authentication.GetExternalLoginInfoAsync();在RegisterExternal操作内返回null.
// POST api/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(); }
我不想对我的Web API进行3次调用,要求外部登录,然后转到该URL并在Web浏览器中进行身份验证,以访问Facebook访问令牌,然后使用我需要收集的访问令牌和Cookie调用RegisterExternal端点在这些电话之间.
正如我所说,除了Facebook Ids,我没有改变任何模板.仍然代码如下.
public partial class Startup { public static OAuthAuthorizationServerOptions OAuthOptions { get; private 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) { // Configure the db context and user manager to use a single instance per request app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); // 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.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 = true }; // Enable the application to use bearer tokens to authenticate users app.USEOAuthBearerTokens(OAuthOptions); app.UseFacebookAuthentication( appId: "xxxxxxxxxxxxxxx",appSecret: "xxxxxxxxxxxxxxxxxxxxxxxx"); } }
据我所知,Web API不需要Cookie,当我从令牌端点获得本地令牌,但为什么在执行ExternalRegister时首先需要Cookie
WebApiConfig类看起来像这样,不应该config.SuppressDefaultHostAuthentication();避免任何Cookie的需求
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services // Configure Web API to use only bearer token authentication. config.SuppressDefaultHostAuthentication(); config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional } ); } }
我不知道我是否在这里错过了这一点.我的意图是不需要在本机iOS应用程序中使用Web浏览器进行令牌.那就是Facebook SDK获取访问令牌,并使用该调用RegisterExternal获取本地令牌并创建该用户身份.
我做了我的功课,我坚持这个想法.
思想赞赏!
解决方法
它不直接接受任何外部令牌.
事情是.. MVC 5正在为我们照顾一切,即从社会媒体收集令牌并验证/处理它.之后,它生成一个本地令牌.
RegisterExternal方法还需要维护Cookie,解决方案不会.
我写了一个blog post,这将详细解释.在下面添加了直接的答案.我的目标是使其融合并成为默认MVC Web API的登录/注册流程的组成部分,以确保其易于理解.
在下面的解决方案之后,授权属性必须如下才能工作,否则您将获得未经授权的响应.
[Authorize] [HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalBearer)] [HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie)]
如果要仅允许令牌使用API,请使用ExternalBearer,如果要仅允许记录的Cookie使用API即从网站使用ApplicationCookie.如果您要允许两者的API,用户都可以.
将此操作添加到AccountController.cs
// POST api/Account/RegisterExternalToken [OverrideAuthentication] [AllowAnonymous] [Route("RegisterExternalToken")] public async Task<IHttpActionResult> RegisterExternalToken(RegisterExternalTokenBindingModel model) { if (!ModelState.IsValid) { return BadRequest(ModelState); } ExternalLoginData externalLogin = await ExternalLoginData.FromToken(model.Provider,model.Token); if (externalLogin == null) { return InternalServerError(); } if (externalLogin.LoginProvider != model.Provider) { Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie); return InternalServerError(); } ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,externalLogin.ProviderKey)); bool hasRegistered = user != null; ClaimsIdentity identity = null; IdentityResult result; if (hasRegistered) { identity = await UserManager.CreateIdentityAsync(user,OAuthDefaults.AuthenticationType); IEnumerable<Claim> claims = externalLogin.GetClaims(); identity.AddClaims(claims); Authentication.SignIn(identity); } else { user = new ApplicationUser() { Id = Guid.NewGuid().ToString(),UserName = model.Email,Email = model.Email }; result = await UserManager.CreateAsync(user); if (!result.Succeeded) { return GetErrorResult(result); } var info = new ExternalLoginInfo() { DefaultUserName = model.Email,Login = new UserLoginInfo(model.Provider,externalLogin.ProviderKey) }; result = await UserManager.AddLoginAsync(user.Id,info.Login); if (!result.Succeeded) { return GetErrorResult(result); } identity = await UserManager.CreateIdentityAsync(user,OAuthDefaults.AuthenticationType); IEnumerable<Claim> claims = externalLogin.GetClaims(); identity.AddClaims(claims); Authentication.SignIn(identity); } AuthenticationTicket ticket = new AuthenticationTicket(identity,new AuthenticationProperties()); var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow; ticket.Properties.IssuedUtc = currentUtc; ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365)); var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket); Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",accessToken); // Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint JObject token = new JObject( new JProperty("userName",user.UserName),new JProperty("id",user.Id),new JProperty("access_token",accessToken),new JProperty("token_type","bearer"),new JProperty("expires_in",TimeSpan.FromDays(365).TotalSeconds.ToString()),new JProperty(".issued",currentUtc.ToString("ddd,dd MMM yyyy HH':'mm':'ss 'GMT'")),new JProperty(".expires",currentUtc.Add(TimeSpan.FromDays(365)).ToString("ddd,dd MMM yyyy HH:mm:ss 'GMT'")) ); return Ok(token); }
将此帮助方法添加到AccountController.cs中的助手区域中的ExternalLoginData类
public static async Task < ExternalLoginData > FromToken(string provider,string accessToken) { string verifyTokenEndPoint = "",verifyAppEndpoint = ""; if (provider == "Facebook") { verifyTokenEndPoint = string.Format("https://graph.facebook.com/me?access_token={0}",accessToken); verifyAppEndpoint = string.Format("https://graph.facebook.com/app?access_token={0}",accessToken); } else if (provider == "Google") { // not implemented yet return null; //verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}",accessToken); } else { return null; } HttpClient client = new HttpClient(); Uri uri = new Uri(verifyTokenEndPoint); HttpResponseMessage response = await client.GetAsync(uri); ClaimsIdentity identity = null; if (response.IsSuccessStatusCode) { string content = await response.Content.ReadAsStringAsync(); dynamic iObj = (Newtonsoft.Json.Linq.JObject) Newtonsoft.Json.JsonConvert.DeserializeObject(content); uri = new Uri(verifyAppEndpoint); response = await client.GetAsync(uri); content = await response.Content.ReadAsStringAsync(); dynamic appObj = (Newtonsoft.Json.Linq.JObject) Newtonsoft.Json.JsonConvert.DeserializeObject(content); identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType); if (provider == "Facebook") { if (appObj["id"] != Startup.facebookAuthOptions.AppId) { return null; } identity.AddClaim(new Claim(ClaimTypes.NameIdentifier,iObj["id"].ToString(),ClaimValueTypes.String,"Facebook","Facebook")); } else if (provider == "Google") { //not implemented yet } } 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) }; } }
最后由该操作使用的RegisterExternalTokenBindingModel.
public class RegisterExternalTokenBindingModel { [required] [Display(Name = "Email")] public string Email { get; set; } [required] [Display(Name = "Token")] public string Token { get; set; } [required] [Display(Name = "Provider")] public string Provider { get; set; } }
是的,我们在注册时传递电子邮件以及令牌细节,这不会导致您在使用Twitter时更改代码,因为Twitter不提供用户电子邮件.我们验证令牌来自我们的应用程序.一旦电子邮件注册,被黑客或别人的令牌不能用于更改电子邮件或获取该电子邮件的本地令牌,因为它将始终为实际的用户授予社交令牌的本地令牌,无论发送的电子邮件如何.
RegisterExternalToken端点用于以两种方式获取令牌,即注册用户并发送本地令牌,或者如果用户已注册,则发送令牌.