可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Using the new OAuthWebSecurity for authenticating with Facebook, I added the email permission on my Facebook application. Now, as I can read, I need to define a scope to be able to actually get the email in the result. So far without the scope I'm not getting the users' email and am not sure why as I can not see where to define the "scope".
It's just a rip of the ASP.NET MVC 4 default authenticationcontrollers external login.
回答1:
Firstly, the extraData parameter is not passed to facebook. It is for internal use only. See the following link on how this data can be used on your site:
http://blogs.msdn.com/b/pranav_rastogi/archive/2012/08/24/customizing-the-login-ui-when-using-oauth-openid.aspx
Now, to the meat:
In addition to the methods RegisterFacebookClient
, RegisterYahooClient
etc. in OAuthWebSecurity
, there is also a generic method RegisterClient
. This is the method we will be using for this solution.
This idea germinates from the code provided at:
http://mvc4beginner.com/Sample-Code/Facebook-Twitter/MVC-4-oAuth-Facebook-Login-EMail-Problem-Solved.html
However, we will not be using the hacky approach provided by the solution. Instead, we will create a new class called FacebookScopedClient
which will implement IAuthenticationClient
. Then we will simply register the class using:
OAuthWebSecurity.RegisterClient(new FacebookScopedClient("your_app_id", "your_app_secret"), "Facebook", null);
in AuthConfig.cs
The code for the class is:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
public class FacebookScopedClient : IAuthenticationClient
{
private string appId;
private string appSecret;
private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id=";
public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?";
public const string graphApiMe = "https://graph.facebook.com/me?";
private static string GetHTML(string URL)
{
string connectionString = URL;
try
{
System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString);
myRequest.Credentials = CredentialCache.DefaultCredentials;
//// Get the response
WebResponse webResponse = myRequest.GetResponse();
Stream respStream = webResponse.GetResponseStream();
////
StreamReader ioStream = new StreamReader(respStream);
string pageContent = ioStream.ReadToEnd();
//// Close streams
ioStream.Close();
respStream.Close();
return pageContent;
}
catch (Exception)
{
}
return null;
}
private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
{
string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" + HttpUtility.UrlEncode(redirectURI) + "&client_secret=" + appSecret + "&code=" + accessCode);
if (token == null || token == "")
{
return null;
}
string data = GetHTML(graphApiMe + "fields=id,name,email,gender,link&access_token=" + token.Substring("access_token=", "&"));
// this dictionary must contains
Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return userData;
}
public FacebookScopedClient(string appId, string appSecret)
{
this.appId = appId;
this.appSecret = appSecret;
}
public string ProviderName
{
get { return "Facebook"; }
}
public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl)
{
string url = baseUrl + appId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=email";
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.OriginalString;
//From this we need to remove code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
IDictionary<string, string> userData = GetUserData(code, rawUrl);
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["email"];
userData.Remove("id");
userData.Remove("email");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
}
now in the
public ActionResult ExternalLoginCallback(string returnUrl)
method in AccountController
, result.ExtraData
should have the email.
Edit: I missed some code in this post. I am adding it below:
public static class String
{
public static string Substring(this string str, string StartString, string EndString)
{
if (str.Contains(StartString))
{
int iStart = str.IndexOf(StartString) + StartString.Length;
int iEnd = str.IndexOf(EndString, iStart);
return str.Substring(iStart, (iEnd - iStart));
}
return null;
}
}
Cheers!
回答2:
Update your NuGet package in your MVC4 Internet project.
DotNetOpenAuthCore. It will automatically update all dependencies.
Now result.UserName will contain the email adress instead of your name.
[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
{
return RedirectToLocal(returnUrl);
}
if (User.Identity.IsAuthenticated)
{
// If the current user is logged in add the new account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
return RedirectToLocal(returnUrl);
}
else
{
// User is new, ask for their desired membership name
string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
ViewBag.ReturnUrl = returnUrl;
return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
}
}
The reason for this?
https://github.com/AArnott/dotnetopenid/blob/a9d2443ee1a35f13c528cce35b5096abae7128f4/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs has been updated in latest NuGet package.
The commit with the fix:
https://github.com/AArnott/dotnetopenid/commit/a9d2443ee1a35f13c528cce35b5096abae7128f4
回答3:
I used Varun's answer, but I had to make a small modification to get it to work for my app being hosted on AppHarbor.
AppHarbor has to do some funky stuff with the port number in urls to handle load balancing. You can read a little more about it here. In short, getting the AbsoluteUri of the current request while hosting on AppHarbor may return a uri with a port number other than 80. This causes problems with Facebook authentication, because they expect your return url to be the one you specified when creating your app.
The problem comes in at string rawUrl = context.Request.Url.OriginalString;
in VerifyAuthentication()
. If you use this code, rawUrl
may contain some port number other than 80, causing the Facebook authentication to fail. Instead, replace that line with
string rawUrl = GetRawUrl(context.Request.Url);
and add the GetRawUrl()
function to the class:
public static string GetRawUrl(Uri url)
{
var port = url.Port;
if (SettingsHelper.GetHostingService() == HostingServices.AppHarbor)
port = 80;
return new UriBuilder(url)
{
Port = port
}.Uri.AbsoluteUri;
}
You will need to replace if (SettingsHelper.GetHostingService() == HostingServices.AppHarbor)
with your own logic for determining whether or not your application is running on AppHarbor.
回答4:
I wrote my own solution to this problem. I extended the OAuth2Client to take advantage of it work and used facebook scope and other features to retrieve additional user data.
I posted my own solution here, I hope it will help someone!
回答5:
Since FB made "Use Strict Mode for Redirect URIs" mandatory the RewriteRequest is needed (as for Google Oauth).
Add the call below in callback processing before OAuthWebSecurity.VerifyAuthentication.
FacebookScopedClient.RewriteRequest();
FacebookScopedClient Class
using System;
using System.Collections.Generic;
using System.Text;
using DotNetOpenAuth.AspNet;
using System.Web;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace UserAccounts.WebApi.ExternalLogin
{
// Thnks to Har Kaur https://www.c-sharpcorner.com/blogs/facebook-integration-by-using-oauth and https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2/blob/master/DotNetOpenAuth.GoogleOAuth2/GoogleOAuth2Client.cs
public class FacebookScopedClient : IAuthenticationClient
{
private string appId;
private string appSecret;
private static string providerName = "Facebook";
private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id=";
public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?";
public const string graphApiMe = "https://graph.facebook.com/me?";
private static string GetHTML(string URL)
{
string connectionString = URL;
try
{
System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString);
myRequest.Credentials = CredentialCache.DefaultCredentials;
//// Get the response
WebResponse webResponse = myRequest.GetResponse();
Stream respStream = webResponse.GetResponseStream();
////
StreamReader ioStream = new StreamReader(respStream);
string pageContent = ioStream.ReadToEnd();
//// Close streams
ioStream.Close();
respStream.Close();
return pageContent;
}
catch (WebException ex)
{
StreamReader reader = new StreamReader(ex.Response.GetResponseStream());
string line;
StringBuilder result = new StringBuilder();
while ((line = reader.ReadLine()) != null)
{
result.Append(line);
}
}
catch (Exception)
{
}
return null;
}
private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
{
string value = "";
string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" +
HttpUtility.UrlEncode(redirectURI) + "&client_secret=" +
appSecret + "&code=" + accessCode);
if (token == null || token == "")
{
return null;
}
if (token != null || token != "")
{
if (token.IndexOf("access_token") > -1)
{
string[] arrtoken = token.Replace("\''", "").Split(':');
string[] arrval = arrtoken[1].ToString().Split(',');
value = arrval[0].ToString().Replace("\"", "");
}
}
string data = GetHTML(graphApiMe + "fields=id,name,email,gender,link&access_token=" + value);
// this dictionary must contains
Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return userData;
}
public FacebookScopedClient(string appId, string appSecret)
{
this.appId = appId;
this.appSecret = appSecret;
}
public string ProviderName
{
get { return providerName; }
}
public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl)
{
var uriBuilder = new UriBuilder(returnUrl);
uriBuilder.Query = "";
var newUri = uriBuilder.Uri;
string returnUrlQuery = HttpUtility.UrlEncode(returnUrl.Query);
string url = baseUrl + appId + "&scope=email" + "&state=" + returnUrlQuery + "&redirect_uri=" + HttpUtility.UrlEncode(newUri.ToString());
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.OriginalString;
//From this we need to remove code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
var uriBuilder = new UriBuilder(rawUrl);
uriBuilder.Query = "";
var newUri = uriBuilder.Uri;
IDictionary<string, string> userData = GetUserData(code, newUri.ToString());
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["email"];
userData.Remove("id");
userData.Remove("email");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
/// <summary>
/// Facebook requires that all return data be packed into a "state" parameter.
/// This should be called before verifying the request, so that the url is rewritten to support this.
/// Thnks to Matt Johnson mj1856 https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2/blob/master/DotNetOpenAuth.GoogleOAuth2/GoogleOAuth2Client.cs
/// </summary>
///
public static void RewriteRequest()
{
var ctx = HttpContext.Current;
var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
if (stateString == null || !stateString.Contains("__provider__=" + providerName)) return;
var q = HttpUtility.ParseQueryString(stateString);
q.Add(ctx.Request.QueryString);
q.Remove("state");
ctx.RewritePath(ctx.Request.Path + "?" + q);
}
}
}
回答6:
I faced the same problem here.
The only way I found to pass the "scope" parameter to facebook was coding my own OAuth client.
To do it, you must extend and implement the abstract methods of DotNetOpenAuth.AspNet.Clients.OAuth2Client.
On the GetServiceLoginUrl method you can add the scope parameter to the url.
So, when you call the OAuthWebSecurity.VerifyAuthentication() method, the AuthenticationResult.UserName provides the user's email.
An example can be found here.
Good luck.
回答7:
It can be done...like this:
var fb = new Dictionary<string, object>();
fb.Add("scope", "email,publish_actions");
OAuthWebSecurity.RegisterFacebookClient(
appId: ConfigurationManager.AppSettings["FacebookAppId"],
appSecret: ConfigurationManager.AppSettings["FacebookAppSecret"],
displayName: "FaceBook",
extraData: fb);