11

設置: 僅使用Web API的新MVC5項目。新增了Facebook AppId和Secret。帶有Facebook訪問令牌的MVC 5 Web API無需Cookie Cookie即可訪問RegisterExternal

通過傳入用戶名和密碼,我可以從Token端點獲得我的Web API的令牌。然後使用該標記進行進一步調用。

我想與Facebook SDK的iOS應用程序的幫助下,註冊新用戶。 我正在使用Facebook SDK獲取訪問令牌。 (假設在這一點上,我有一個訪問令牌)。

我知道的下一件事是通過在Authorization標頭中傳遞此令牌來調用api/Account/RegisterExternal端點Bearer [Access Token]但這會導致500服務器錯誤。

我想我知道原因,Cookie丟失了。我用來自Fidler的cookie進行了同樣的調用,並且工作正常。 (通過去ExternalLogins端點提供的URL接收Cookie)。 由於Cookie中缺少await 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(); 
    } 

我不想讓3個電話到我的Web API,要求外部登錄,然後轉到該URL,並在Web瀏覽器爲Facebook訪問令牌身份驗證,然後調用RegisterExternal端點與訪問令牌和我需要收集這些電話之間的Cookie。

正如我所說我沒有改變任何模板,除了Facebook的ID。代碼仍然如下。

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和時,我有本地令牌從Token端點出現真實的,但爲什麼它需要的Cookie在第一時間做ExternalRegister 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 } 
      ); 
     } 
    } 

如果我錯過了點這裏..我的意圖是不需要使用Web瀏覽器在原生iOS應用的令牌,我不知道。即Facebook SDK獲取訪問令牌並使用該呼叫RegisterExternal獲取本地令牌並創建該用戶身份。

我做了我的功課,我被卡在這個想法。 感謝讚賞!

+0

找到任何解決方案嗎?我會很感興趣,因爲我有同樣的問題。 – Freddy

+0

@Freddy是的,我結束了將不同的作品放在一起來創建我自己的解決方案,效果很好。將張貼它。目前出城,如果我沒有發佈,請在4天后給我打電話。 – CodetrixStudio

+0

這將是真正慷慨的你。我目前正在創建我自己的解決方案... – Freddy

回答

17

我誤認爲它接受cookie的社會令牌! 它不直接接受任何外部令牌。

事情是.. MVC 5正在照顧我們的一切,即收集Social Medias中的令牌並驗證/處理它。之後它會生成一個本地令牌。

RegisterExternal方法也需要cookies來維護,解決方案沒有。

我已經寫了blog post這將在詳細說明解釋。在下面添加了簡單的答案。我旨在使它融合並感受默認MVC Web API的登錄/註冊流程不可或缺的部分,以確保其易於理解。

在下面的解決方案之後,Authorize屬性必須如下工作,否則您將得到未經授權的響應。

[Authorize] 
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalBearer)] 
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie)] 

使用ExternalBearer,如果你希望只允許令牌使用API​​,使用ApplicationCookie如果你希望只允許登錄cookie的使用API​​,即從一個網站。用戶,如果你想允許這兩個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); 
} 

這個輔助方法添加到在助手區域ExternalLoginDataAccountController.cs

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不提供用戶的電子郵件更改代碼。我們驗證令牌來自我們的應用程序。一旦電子郵件註冊,被黑客攻擊或其他人的令牌不能用於更改電子郵件或獲取該電子郵件的本地令牌,因爲無論發送的電子郵件如何,它都會爲社交令牌的實際用戶返回本地令牌。

端點的工作方式是以兩種方式獲取令牌,即註冊用戶併發送本地令牌,或者如果用戶已經註冊,則發送令牌。

+0

哇,這太棒了 - 棒極了!儘管我已經通過使用2個自己的端點(一個用於註冊,一個用於登錄並將第三方ID放在我自己的用戶表中)實現了我自己的解決方案,但我會盡快嘗試。正如我已經整合了推特,我可能將不得不調整你的解決方案,因爲twitter使用appkey,appsecret以及usertoken和usertokensecret來驗證......你怎麼看?另一點是你在哪裏配置社交appkeys等?在StartupClass中使用app.useFacebookAuth? – Freddy

+0

另一點是你在哪裏配置社交appkeys等?在StartupClass中使用app.useFacebookAuth還是根本不?我是否正確,您只需要社交登錄並沒有應用驗證? – Freddy

+0

接下來的問題是twitter,你沒有從社交登錄中獲得電子郵件地址。所以你必須在客戶端的另一個對話框中詢問你的用戶。 – Freddy