31

Zajmuję się tworzeniem strony internetowej w ASP.NET MVC 5 (obecnie używa wersji RC1). Witryna będzie korzystać z Facebooka do uwierzytelniania użytkowników i pobierania początkowych danych profilu.Jak uzyskać dostęp do prywatnych informacji na Facebooku za pomocą ASP.NET Identity (OWIN)?

Do systemu uwierzytelniania używam nowego silnika ASP.NET Identity opartego na OWIN (http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx), ponieważ znacznie upraszcza proces uwierzytelniania z zewnętrznymi dostawcami.

Problem polega na tym, że po pierwszym zalogowaniu się użytkownika chcę uzyskać jego adres e-mail z profilu na Facebooku, ale dane te nie są uwzględniane w generowanych roszczeniach. Więc myślałem o tych alternatyw, aby uzyskać adres:

  1. instruować silnik Identity ASP.NET zawierać adres email zbiór danych, które są pobierane z Facebookiem i następnie przekształcany zastrzeżeń. Nie wiem, czy to w ogóle możliwe.

  2. Użyj Facebook wykres API (https://developers.facebook.com/docs/getting-started/graphapi) do odzyskać adres e-mail przy użyciu ID użytkownika Facebook (który jest zawarte w danych roszczeń). Ale to nie zadziała, jeśli użytkownik ma ustawić swój adres e-mail jako prywatny.

  3. Użyj wykresu Facebook API, ale określenie "ja" zamiast Facebook User ID (https://developers.facebook.com/docs/reference/api/user). Ale wymagany jest token dostępu i nie wiem, jak to zrobić (lub jeśli w ogóle jest to możliwe) odzyskać token dostępu, który ASP.NET wykorzystuje do uzyskiwania danych użytkownika.

Więc pytanie brzmi:

  1. Jak mogę polecić silnik Identity ASP.NET do pobierania dodatkowych informacji z Facebookiem i umieścić go w zastrzeżeniach danych?

  2. Lub alternatywnie, w jaki sposób mogę pobrać wygenerowany token dostępu, aby mógł samodzielnie zapytać Facebooka?

Dziękujemy!

Uwaga: dla systemu uwierzytelniania moja aplikacja korzysta z kodu opartego na projekcie próbki połączonej w ten SO odpowiedzieć: https://stackoverflow.com/a/18423474/4574

Odpowiedz

24

Aby otrzymać dodatkowe informacje od facebooku można określić zakresy chcesz to podczas konfigurowania facebook opcje uwierzytelniania. Uzyskiwanie dodatkowych informacji, które jest pobierane mogą być osiągnięte poprzez wdrożenie metody OnAuthenticated usługodawcy tak:

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions() 
{ 
    Provider = new FacebookAuthenticationProvider() 
    { 
     OnAuthenticated = (context) => 
      { 
       // All data from facebook in this object. 
       var rawUserObjectFromFacebookAsJson = context.User; 

       // Only some of the basic details from facebook 
       // like id, username, email etc are added as claims. 
       // But you can retrieve any other details from this 
       // raw Json object from facebook and add it as claims here. 
       // Subsequently adding a claim here will also send this claim 
       // as part of the cookie set on the browser so you can retrieve 
       // on every successive request. 
       context.Identity.AddClaim(...); 

       return Task.FromResult(0); 
      } 
    } 
}; 

//Way to specify additional scopes 
facebookOptions.Scope.Add("..."); 

app.UseFacebookAuthentication(facebookOptions); 

Per kodu here widzę e-mail jest już pobrana i dodawany jako roszczenie tutaj jeśli facebook wysłał. Nie jesteś w stanie tego zobaczyć?

+3

Dziękujemy! Sztuką jest wywołanie 'facebookOptions.Scope.Add (" email ")', a następnie, jak mówisz, dane e-mail są automatycznie dodawane jako roszczenie bez konieczności analizowania danych json. Jednak mam teraz nowy problem: podczas używania twojego kodu, wywołanie 'AuthenticationManager.GetExternalIdentity' w wywołaniu zwrotnym zwróci wartość null zamiast instancji' ClaimsIdentity'. Czy masz pojęcie, co może się dziać? (i tak, dodaję odpowiedni identyfikator aplikacji i sekret do obiektu facebookOptions) – Konamiman

+1

Ok, znalazłem rozwiązanie do zbadania stąd: http://forums.asp.net/p/1927914/5516103.aspx?p=True&t= 635154678323993438. Następujące elementy muszą zostać dodane do inicjowania obiektu facebookOptions: 'SignInAsAuthenticationType =" External "'. Przyjmuję twoją odpowiedź, ale może chcesz ją edytować, aby zawierała te sztuczki. :-) – Konamiman

+1

Czy możesz podzielić się przykładowym kodem, aby pokazać, w jaki sposób uzyskałeś dodatkowe informacje (np. BirthDate) w zadaniu callback actionresult? – Santosh

2

Oto kilka kroków, które pomogą. Jestem w trakcie pisania posta na blogu, ale zajmie to trochę czasu ... - Dodaj zakresów w dostawcę Fb i dodać dane zwracane z FB jako roszczenia

app.UseFacebookAuthentication(new FacebookAuthenticationOptions() 
     { 
      AppId = "", 
      AppSecret = "", 
      //Scope = "email,user_about_me,user_hometown,friends_about_me,friends_photos", 
      Provider = new FacebookAuthenticationProvider() 
      { 
       OnAuthenticated = async context => 
       { 
        foreach (var x in context.User) 
        { 
         context.Identity.AddClaim(new System.Security.Claims.Claim(x.Key, x.Value.ToString())); 
        } 
        //Get the access token from FB and store it in the database and use FacebookC# SDK to get more information about the user 
        context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken)); 
       } 
      }, 
      SignInAsAuthenticationType = "External", 
     });   
  • użyć token dostępu i nazywają Facebook C# SDK, aby uzyskać listę znajomych dla użytkownika

    var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity; 
        var access_token = claimsIdentity.FindAll("FacebookAccessToken").First().Value; 
        var fb = new FacebookClient(access_token); 
        dynamic myInfo = fb.Get("/me/friends"); 
        var friendsList = new List<FacebookViewModel>(); 
        foreach (dynamic friend in myInfo.data) 
        { 
         friendsList.Add(new FacebookViewModel() { Name = friend.name, ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large" }); 
         //Response.Write("Name: " + friend.name + "<br/>Facebook id: " + friend.id + "<br/><br/>"); 
        } 
    
+0

podążyłem za twoją próbką, ale claimIdentity = HttpContext.User.Identity jako ClaimsIdentity zawiera tylko roszczenie LOKALNE, a nie facebookowe. : ~/ –

+0

Czy możesz spróbować HttpContext.Current.GetOwinContext(). Authentication.User? – Praburaj

+0

Świetnie, więc jak uzyskać adres e-mail użytkownika z zewnętrznego logowania MS Live z zakresami "wl.basic" i "wl.emails"? – subsci

29

Utwórz nowy obiekt Microsoft.Owin.Security.Facebook.AuthenticationOptions w pliku Startup.ConfigureAuth (StartupAuth.cs), przekazując go składnikowi FacebookAppId, FacebookAppSecret i nowemu dostawcy uwierzytelniania. Użyjesz wyrażenia lambda do przekazania metody OnAuthenticated, aby dodać kod do roszczenia do tożsamości, które zawierają wartości wyodrębnione z context.Identity. Będzie to domyślnie obejmować access_token. Musisz dodać e-mail do zakresu. Inne właściwości użytkownika są dostępne z context.User (patrz link na dole na przykład).

StartUp.Auth.cs

// Facebook : Create New App 
// https://dev.twitter.com/apps 
if (ConfigurationManager.AppSettings.Get("FacebookAppId").Length > 0) 
{ 
    var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions() 
    { 
     AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"), 
     AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"), 
     Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider() 
     { 
      OnAuthenticated = (context) => 
       { 
        context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook")); 
        context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:email", context.Email, XmlSchemaString, "Facebook")); 
        return Task.FromResult(0); 
       } 
     } 

    }; 
    facebookOptions.Scope.Add("email"); 
    app.UseFacebookAuthentication(facebookOptions); 
} 

W AccountController, wyodrębnić ClaimsIdentity z AuthenticationManager pomocą cookie zewnętrznej. Dodaję go następnie do tożsamości utworzonej za pomocą pliku cookie aplikacji. Zignorowałem wszelkie roszczenia, które zaczynają się od "... schemas.xmlsoap.org/ws/2005/05/identity/claims", ponieważ wydawało się, że zepsuło to logowanie.

AccountController.cs

private async Task SignInAsync(CustomUser user, bool isPersistent) 
{ 
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); 
    var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); 

// Extracted the part that has been changed in SignInAsync for clarity. 
    await SetExternalProperties(identity); 

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity); 
} 

private async Task SetExternalProperties(ClaimsIdentity identity) 
{ 
    // get external claims captured in Startup.ConfigureAuth 
    ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie); 

    if (ext != null) 
    { 
     var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims"; 
     // add external claims to identity 
     foreach (var c in ext.Claims) 
     { 
      if (!c.Type.StartsWith(ignoreClaim)) 
       if (!identity.HasClaim(c.Type, c.Value)) 
        identity.AddClaim(c); 
     } 
    } 
} 

I wreszcie, chcę, aby wyświetlić cokolwiek wartości nie są od lokalnych władz. Utworzono częściowy widok _WewnętrznyUserPropertiesListPartial, który pojawia się na /Account/Manage page. Otrzymuję oświadczenia, które wcześniej zapisałem w pliku AuthenticationManager.User.Claims, a następnie przekazuję je do widoku.

AccountController.cs

[ChildActionOnly] 
public ActionResult ExternalUserPropertiesList() 
{ 
    var extList = GetExternalProperties(); 
    return (ActionResult)PartialView("_ExternalUserPropertiesListPartial", extList); 
} 

private List<ExtPropertyViewModel> GetExternalProperties() 
{ 
    var claimlist = from claims in AuthenticationManager.User.Claims 
        where claims.Issuer != "LOCAL AUTHORITY" 
        select new ExtPropertyViewModel 
        { 
         Issuer = claims.Issuer, 
         Type = claims.Type, 
         Value = claims.Value 
        }; 

    return claimlist.ToList<ExtPropertyViewModel>(); 
} 

I tylko żeby być dokładny, widok:

_ExternalUserPropertiesListPartial.cshtml

@model IEnumerable<MySample.Models.ExtPropertyViewModel> 

@if (Model != null) 
{ 
    <legend>External User Properties</legend> 
    <table class="table"> 
     <tbody> 
      @foreach (var claim in Model) 
      { 
       <tr> 
        <td>@claim.Issuer</td> 
        <td>@claim.Type</td> 
        <td>@claim.Value</td> 
       </tr> 
      } 
     </tbody> 
    </table> 
} 

Przykład pracę i kompletny kod jest na GitHub: https://github.com/johndpalm/IdentityUserPropertiesSample

I wszelkie opinie, poprawne jony lub ulepszenia byłyby docenione.

+0

Gdy próbuję tego i dodaje oświadczenie "accesstoken", kolejne wywołanie metody User.Identity.GetUserId() zwraca wartość null. Po prostu dodanie 1 roszczenia wydaje się przełamać obiekt User.Identity ... – creatiive

21

on pracował dla mnie tylko to w Startup.Auth:

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()  { 
    AppId = "*", 
    AppSecret = "**" 
}; 
facebookOptions.Scope.Add("email"); 
app.UseFacebookAuthentication(facebookOptions); 

A następnie w metodzie ExternalLoginCallback lub ExternalLoginConfirmation dostaniesz maila z:

ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie); 
var email = ext.Claims.First(x => x.Type.Contains("emailaddress")).Value; 
+3

możesz go użyć dla drugiego fragmentu kodu: var loginInfo = poczekaj na AuthenticationManager.GetExternalLoginInfoAsync(); string email = loginInfo.Email; –

4

Musisz utworzyć instancję FacebookAuthenticationOptions i skonfiguruj Provider. Provider zawiera zdarzenie o nazwie OnAuthenticated, które jest wyzwalane podczas logowania.

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions 
{ 
    Provider = new FacebookAuthenticationProvider() 
    { 
     OnAuthenticated = (context) => 
     { 
      context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook")); 

      return Task.FromResult(0); 
     } 
    }, 

    // You can store these on AppSettings 
    AppId = ConfigurationManager.AppSettings["facebook:AppId"], 
    AppSecret = ConfigurationManager.AppSettings["facebook:AppSecret"] 
}; 

app.UseFacebookAuthentication(facebookOptions); 

W powyższym kodzie mam dostępu do access_token przez context.AccessToken i dodać go do Claims bieżącego użytkownika loggedin.

Aby uzyskać dostęp do tej wartości później trzeba to zrobić:

var owinContext = HttpContext.GetOwinContext(); 
var authentication = owinContext.Authentication; 
var user = autentication.User; 
var claim = (user.Identity as ClaimsIdentity).FindFirst("urn:facebook:access_token"); 

string accessToken; 
if (claim != null) 
    accessToken = claim.Value; 

Aby uprościć to wszystko, można utworzyć BaseController i uczynić całe Controllers Dziedziczy z niego.

Kod BaseController byłoby:

public class BaseController : Controller 
{ 
    public IOwinContext CurrentOwinContext 
    { 
     get 
     { 
      return HttpContext.GetOwinContext(); 
     } 
    } 

    public IAuthenticationManager Authentication 
    { 
     get 
     { 
      return CurrentOwinContext.Authentication; 
     } 
    } 

    public new ClaimsPrincipal User 
    { 
     get 
     { 
      return Authentication.User; 
     } 
    } 

    public ClaimsIdentity Identity 
    { 
     get 
     { 
      return Authentication.User.Identity as ClaimsIdentity; 
     } 
    } 

    public string FacebookAccessToken 
    { 
     get 
     { 
      var claim = Identity.FindFirst("urn:facebook:access_token"); 

      if (claim == null) 
       return null; 

      return claim.Value; 
     } 
    } 
} 

Następnie, aby uzyskać tokenu dostępu w kodzie wystarczy dostęp do właściwości FacebookAccessToken.

string accessToken = FacebookAccessToken; 

Jest możliwe, aby pobrać jakieś inne wartości jako

context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:username", 
    context.User.Value<string>("username"), ClaimValueTypes.String, "Facebook")); 

context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:name", 
    context.User.Value<string>("name"), ClaimValueTypes.String, "Facebook")); 

pamiętać, że nie wszystkie pola będą dostępne, aby otrzymać e-maila trzeba wymagać Scope mail.

facebookOptions.Scope.Add("email"); 

Następnie przejść na razie OnAuthenticated jak

context.User.Value<string>("email"); 
0

moich paru groszy do wszystkich odpowiedzi ... jeśli nadal chcesz zapytać Facebooka siebie, warto spojrzeć na już istniejących Facebook pakiet. Zapewnia świetną funkcjonalność, która już została zaimplementowana, więc nie trzeba jej ponownie implementować samodzielnie ... na przykład w aplikacji ASP.NET MVC można znaleźć here.

Powiązane problemy