I've accidentally jumped into the world of cookies and am trying to understand what's going on. I have a web app developed in Visual Studio 20120/C# using FormsAuthentication. When I first developed the app, I created a few fields to store in the authentication cookie: personID, firstName, and admin, the string looks like this: 777|Jimmy|1. Everything has worked well since then. Now I've added a fourth field to the end of the blur called "secBlur". When I do this and try to retrieve the value of secBlur, it tells me the array range is out of bounds because the earlier version of the cookie did not contain this field...makes sense. I've spent the past couple of days trying to rewrite the validity check for my cookie, and I thought I had everything figured out. However, when I go to write the new userData string into the cookie, it doesn't appear to be doing it. My code is below, I'll try to walk through what I'm doing...
In the page_load of my master page, the first thing I'm doing is making a call to a cookie class I created to check that the cookie is the correct version:
protected void Page_Load(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
authCookie ac = new authCookie();
ac.validate();
LoginName ct = (LoginName)loginStatus.FindControl("HeadLoginName");
if (ct != null)
{
formValues fv = new formValues();
ct.FormatString = fv.firstName;
}
}
}
My entire cookie class is below. In the Validate method I'm checking for the existence of the cookie and then checking to see that it is the correct version and that userData exists. If it's not the correct version or userData does not exist I call the getUserData method to retrieve the most current info for this year, create a new ticket, store the ticket into the cookie, and then save the cookie. I think the line saving the cookie is the problem, but I'm not sure.
using System;
using System.Data.SqlClient;
using System.Runtime.Remoting.Contexts;
using System.Web;
using System.Web.Security;
using System.Web.UI.WebControls;
namespace DMC.Classes
{
public class authCookie
{
public void cookiePrep(Login LoginUser)
{
string userData = "unknown|unknown";
// Concat the values into a single string to pass into the cookie
userData = getUserData(LoginUser.UserName);
// Create the cookie that contains the forms authentication ticket
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(LoginUser.UserName, LoginUser.RememberMeSet);
// Get the FormsAuthenticationTicket out of the encrypted cookie
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(3,
ticket.Name,
ticket.IssueDate,
ticket.Expiration,
LoginUser.RememberMeSet,
userData,
ticket.CookiePath);
// Manually add the authCookie to the Cookies collection
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
HttpContext.Current.Response.Cookies.Add(authCookie);
string redirUrl = FormsAuthentication.GetRedirectUrl(LoginUser.UserName, LoginUser.RememberMeSet);
if (redirUrl == null)
redirUrl = "../default.aspx";
HttpContext.Current.Response.Redirect(redirUrl);
}
public string getUserData(string userID)
{
string userData = "";
// Grab this user's firstname, personID, and Admin status
string mySQL = "exec get_adBasicInfo #userName";
string cf = System.Configuration.ConfigurationManager.ConnectionStrings["DistrictAssessmentDWConnectionString"].ConnectionString;
SqlConnection connection = new SqlConnection(cf);
SqlCommand command = new SqlCommand(mySQL, connection);
command.Parameters.AddWithValue("#userName", userID);
connection.Open();
SqlDataReader dr = command.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
userData = string.Concat(dr["personID"], "|", dr["firstName"], "|", dr["secBlur"]);
}
dr.Close();
return userData;
}
public void validate()
{
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
/**********************************************************************************************************************
* Version 3: Added the secBlur field onto the userData string to see if logged in user needs to have sensitive *
* data blurred out (0: Normal; 1: Blur Sensitive Data *
**********************************************************************************************************************/
if ((ticket.Version != 3) || (ticket.UserData == ""))
{
string userData = getUserData(ticket.Name);
FormsAuthenticationTicket newAuthTicket = new FormsAuthenticationTicket(3,
ticket.Name,
ticket.IssueDate,
ticket.Expiration,
ticket.IsPersistent,
userData,
ticket.CookiePath);
authCookie.Value = FormsAuthentication.Encrypt(newAuthTicket);
HttpContext.Current.Response.SetCookie(authCookie);
}
}
}
}
}
At this point control passes back out to the load_page function of my master page and attempts to retrieve the firstName of the user from the cookie by calling my formValues class, below:
using DMC.Classes;
using System.Web;
using System.Web.Security;
namespace DMC.Classes
{
public class formValues : System.Web.Services.WebService
{
public string firstName = getFirstName();
public string personID = getPersonID();
public string secBlur = getSecBlur();
private static string getUserDataString(int ix)
{
string retValue = "";
if (HttpContext.Current.Request.IsAuthenticated)
{
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
if (ticket != null)
{
string[] userData = { "" };
char[] delimiterChar = { '|' };
userData = ticket.UserData.Split(delimiterChar);
retValue = userData[ix];
}
}
}
return retValue;
}
private static string getFirstName()
{
string firstName = getUserDataString(1);
return firstName;
}
private static string getPersonID()
{
string personID = getUserDataString(0);
return personID;
}
private static string getSecBlur()
{
string secBlur = getUserDataString(2);
return secBlur;
}
}
}
On attempting to getFirstName, I'm getting an error in the getUserDataString method when attempting to set the retValue because the userData array is empty. So can somebody please tell me where I'm going wrong?
In my authCookie class, I changed from:
HttpContext.Current.Response.SetCookie(authCookie);
to
HttpContext.Current.Response.Add(authCookie);
I'm not a fan of this though, because from what I read, if the cookie already exists, this does not overwrite the cookie, it will just create a duplicate. But I've been playing around and it's the only thing that seems to work. If somebody has a better solution, please share!!
Related
I have code that works well when I authorize with a Google.Apis.Auth.OAuth2.UserCredential. The same code does not work when I switch to a Google.Apis.Auth.OAuth2.ServiceAccountCredential.
The trouble could be in one of three places:
1). Am I constructing the ServiceAccountCredential correctly?
2). Am I using the ServiceAccountCredential correctly to access the
user's account?
3). Did the GA Admin give the service account proper access to read
user's mail?
This is the code that is not working:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Services;
using Google.Apis.Discovery.v1;
using Google.Apis.Discovery.v1.Data;
using Google.Apis.Util.Store;
string[] asScopes = { GmailService.Scope.GmailModify };
string msApplicationName = "Gmail API .NET Quickstart";
string sClientEmail = "blah#blah.gserviceaccount.com" //service account;
string sUser = "cfo#mydomain.com" //email of the user that I want to read;
string sPrivateKey = "-----BEGIN PRIVATE KEY----- blah"//service account private key;
string[] asScopes = {"https://mail.google.com/"};
//get credential
ServiceAccountCredential oCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(sClientEmail)
{
Scopes = asScopes,
User = sUser //the user to be impersonated
}.FromPrivateKey(sPrivateKey));
// Create Gmail API service.
GmailService oSVC = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = oCred,
ApplicationName = msApplicationName,
});
// List labels.
UsersResource.LabelsResource.ListRequest request = oSVC.Users.Labels.List("me");
IList<Google.Apis.Gmail.v1.Data.Label> labels = request.Execute().Labels; //<--error here.
/* --- fails with:
Google.Apis.Auth.OAuth2.Responses.TokenResponseException was unhandled HResult=-2146233088 Message=Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method.", Uri:"" Source=Google.Apis.Auth
*/
If anyone could help with examples of how to test a ServiceAccountCredential to see if it is constructed correctly, and further, what it has been authorized to, I'd really appreciate it.
These are the credentials set for my ClientID
A nagging question in all this is if I can even create a ServiceAccountCredential from a PrivateKey, as all the examples I have seen use a Certificate, eg:
var certificate = new X509Certificate2("key2.p12", "notasecret",
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
string userEmail = "abc#gmail.com";
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new string[] { Gmail.v1.GmailService.Scope.GmailReadonly }
}.FromCertificate(certificate)
);
YAHOO! I GOT IT WORKING!!
So the key was:
1). DWD must be checked when creating service account.
2). To have the application authorized for EXACTLY the scopes I needed to use. So your Google Admin has got to give your app exactly the scopes you need.
Here is the code:
private const string MC_GOOGLE_APP_NAME = "from-google-00110";
private const string MC_GOOGLE_SERVICE_ACCOUNT = "appname#from-google-00110.iam.gserviceaccount.com";
private const string MC_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\xxxx blah blah blah xxx\n-----END PRIVATE KEY-----\n";
private const string MS_GMAIL_QUERY = "label: inbox, label: unread";
//**** CRITICAL! THIS MUST EXACTLY MATCH THE SCOPES THAT YOUR App IS AUTHORZIED FOR ***
private const string MS_SCOPES ="https://www.googleapis.com/auth/gmail.modify";
private string msEmailAccount = "mike#blue-mosaic.com"; //the email account you are reading, not mine please
//get credential for service account
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(MC_GOOGLE_SERVICE_ACCOUNT)
{
Scopes = MS_SCOPES,
User = msEmailAccount
}.FromPrivateKey(MC_PRIVATE_KEY));
//create a new gmail service
GmailService oSVC = GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = MC_GOOGLE_APP_NAME,
});
List<Google.Apis.Gmail.v1.Data.Message> aoMessageList = new List<Google.Apis.Gmail.v1.Data.Message>();
UsersResource.MessagesResource.ListRequest request = oSVC.Users.Messages.List(msEmailAccount);
request.Q = MS_GMAIL_QUERY;
//keep making requests until all messages are read.
do
{
ListMessagesResponse response = request.Execute();
if (response.Messages != null)
{
aoMessageList.AddRange(response.Messages);
request.PageToken = response.NextPageToken;
}
} while (!String.IsNullOrEmpty(request.PageToken));
//now read the body of each of the new messages
foreach (Message oMsg in aoMessageList)
{
string sMsgID = oMsg.Id;
sState = "Reading Message '" + sMsgID + "'";
// and this one gets a bit nuts. processing GMAIL messages took me a fair amount
// of reading, parsing and decoding, so it required a whole class!!
SaneMessage oThisMsg = new SaneMessage(oSVC, "me", sMsgID);
//and do something with the message
}
So the code above not only shows getting logged in, it also shows reading email. For reading Google Apps email, it required a bunch of parsing to deal with URL encoded Base64 encoded messages that often had weird breaks in them. I wrote a class to handle all this:
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Email_Reader_Service
{
class SaneMessage
{
private string msID = "";
private string msFrom = "";
private string msDate = "";
private string msSubject = "";
private string msBody = "";
public string ID
{
get { return msID; }
}
public string From
{
get { return msFrom; }
}
public DateTime Date
{
get { return Convert.ToDateTime(msDate); }
}
public string Subject
{
get { return msSubject; }
}
public string Body
{
get { return msBody; }
}
public SaneMessage(GmailService service, String userId, String messageId)
{
Google.Apis.Gmail.v1.Data.Message oStupidMessage = service.Users.Messages.Get(userId, messageId).Execute();
string sBackupDate = string.Empty;
foreach (var mParts in oStupidMessage.Payload.Headers)
{
System.Diagnostics.Debug.Print("{0}\t\t\t\t{1}", mParts.Name, mParts.Value);
switch (mParts.Name)
{
case ("X-Google-Original-Date"):
msDate = mParts.Value;
break;
case ("Date"):
sBackupDate = mParts.Value;
break;
case ("From"):
msFrom = mParts.Value;
break;
case ("Subject"):
msSubject = mParts.Value;
break;
}
}
//-----------------------------------------------------------------------------------------------
//the fooking date comes in a plethora of formats. if the timezone name is appended on the end
// the datetime conversion can't convert.
if(msDate.Length == 0)
msDate = sBackupDate;
if (msDate.Contains('('))
msDate= msDate.Substring(0, msDate.LastIndexOf('('));
//-----------------------------------------------------------------------------------------------
if (msDate != "" && msFrom != "")
{
string sEncodedBody;
if (oStupidMessage.Payload.Parts == null && oStupidMessage.Payload.Body != null)
{
sEncodedBody = oStupidMessage.Payload.Body.Data;
}
else
{
sEncodedBody = getNestedParts(oStupidMessage.Payload.Parts, "");
}
///need to replace some characters as the data for the email's body is base64
msBody = DecodeURLEncodedBase64EncodedString(sEncodedBody);
}
}
private string getNestedParts(IList<MessagePart> part, string curr)
{
string str = curr;
if (part == null)
{
return str;
}
else
{
foreach (var parts in part)
{
if (parts.Parts == null)
{
if (parts.Body != null && parts.Body.Data != null)
{
str += parts.Body.Data;
}
}
else
{
return getNestedParts(parts.Parts, str);
}
}
return str;
}
}
/// <summary>
/// Turn a URL encoded base64 encoded string into readable UTF-8 string.
/// </summary>
/// <param name="sInput">base64 URL ENCODED string.</param>
/// <returns>UTF-8 formatted string</returns>
private string DecodeURLEncodedBase64EncodedString(string sInput)
{
string[] asInput = sInput.Split("=".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
string sOutput = string.Empty;
foreach (string sInputPiece in asInput)
{
string sBase46codedBody = sInputPiece.Replace("-", "+").Replace("_", "/"); //get rid of URL encoding, and pull any current padding off.
string sPaddedBase46codedBody = sBase46codedBody.PadRight(sBase46codedBody.Length + (4 - sBase46codedBody.Length % 4) % 4, '='); //re-pad the string so it is correct length.
byte[] data = Convert.FromBase64String(sPaddedBase46codedBody);
sOutput += Encoding.UTF8.GetString(data);
}
System.Diagnostics.Debug.Print("{0}\r\n\r\n", sOutput);
return sOutput;
}
}
}
Using FormsAuthentication, I am creating a FormsAuthenticationTicket, encrypting, adding this to a cookie using Response.Cookies.Add(authCookie). I then do a redirect using Response.Redirect to the original page that was requested. There is code in the Global.asax in the Application_AuthenticateRequest method that looks to retrieve the cookie - HttpCookie authCookie = Context.Request.Cookies[cookieName]. For some reason, however, when it hits the Global.asax code after the redirect is called, there are no cookies in the collection. At this point, I am a bit stumped as to why it is losing the cookie from the collection. Any thoughts as to why this would happen? Right now, I am just working within localhost.
Login Page Code:
string adPath = "LDAP://ldapserveraddress";
LdapAuthentication adAuth = new LdapAuthentication(adPath);
try
{
if (true == adAuth.IsAuthenticated("ES", txtUsername.Text, txtPassword.Text))
{
string groups = adAuth.GetGroups();
//Create the ticket, and add the groups.
bool isCookiePersistent = chkPersist.Checked;
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,
txtUsername.Text, DateTime.Now, DateTime.Now.AddMinutes(60), isCookiePersistent, groups);
//Encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
//Create a cookie, and then add the encrypted ticket to the cookie as data.
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
if (true == isCookiePersistent)
authCookie.Expires = authTicket.Expiration;
//Add the cookie to the outgoing cookies collection.
Response.Cookies.Add(authCookie);
string redirect = FormsAuthentication.GetRedirectUrl(txtUsername.Text, false);
//You can redirect now.
Response.Redirect(redirect,false);
}
else
{
errorLabel.Text = "Authentication did not succeed. Check user name and password.";
}
}
catch (Exception ex)
{
errorLabel.Text = "Error authenticating. " + ex.Message;
}
}
Global.asax Code (Application_AuthenticateRequest):
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if (null == authCookie)
{
//There is no authentication cookie.
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
//Write the exception to the Event Log.
return;
}
if (null == authTicket)
{
//Cookie failed to decrypt.
return;
}
//When the ticket was created, the UserData property was assigned a
//pipe-delimited string of group names.
string[] groups = authTicket.UserData.Split(new char[] { '|' });
//Create an Identity.
GenericIdentity id = new GenericIdentity(authTicket.Name, "LdapAuthentication");
//This principal flows throughout the request.
GenericPrincipal principal = new GenericPrincipal(id, groups);
Context.User = principal;
}`
I was able to resolve my issue by adjusting the data that was being stored in the userData of the FormsAuthenticationTicket. It appears as though the amount of data that I was trying to insert exceeded a maximum. Once I removed, everything works as expected.
Am storing and retreiving the cookie using the coding
public static void SetCookie(string key, string value, int dayExpires)
{
HttpCookie encodedCookie = HttpSecureCookie.Encode(new HttpCookie(key, value));
encodedCookie.Expires = DateTime.Now.AddDays(dayExpires);
HttpContext.Current.Response.Cookies.Remove(key);
HttpContext.Current.Response.Cookies.Add(encodedCookie);
}
public static string GetCookie(string key)
{
string value = string.Empty;
HttpCookie cookie = HttpContext.Current.Request.Cookies[key];
if (cookie != null)
{
// For security purpose, we need to encrypt the value.
HttpCookie decodedCookie = HttpSecureCookie.Decode(cookie);
value = decodedCookie.Value;
}
else
{
SetCookie("currency", "GBP", 1);
if (key.ToUpper() == "CURRENCY")
value = "GBP";
else if (key.ToUpper() == "COUNTRYCODE")
value = "GB";
}
return value;
}
}
am able to store the cookie and also when i try to get the value of currency from the cookie using HttpContext.Current.Request.Cookies[key] where key has currency am getting the value as ""
in the image below you can have the look at the cookies stored
here you can see that you can currency repeated twice. In key [4] currency is "" where as i have my cookie value in key [6]. Any help why the currency is repeated twice when i am removing the key and then adding the key in the immediate lines.
For test purpose i have placed the set and get in the immediate lines. code below
CookieStore.SetCookie("currency", CurrencyCode, 1);
string currencycookie=CookieStore.GetCookie("currency");
Ultimately i must have only one currency where i have the unique key there.
Thanks.
Removing the key like this will not help you , remove the cookie set in the client browser. Either you have to set the expiry date in the past for the existing key. Better solution is to check if the key is available update the value of the cookie rather than removing and adding it.
if (Request.Cookies[key] != null)
{
Response.Cookies[key].Value = "NEW VAalue"
}
else
// create the new cookie key.
See my updated code with logic of removing and adding the new value
if (Request.Cookies["Test"] == null)
{
HttpCookie testCookie = new HttpCookie("Test");
testCookie.Value = "1";
testCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(testCookie);
}
else
{
var c = Request.Cookies["Test"];
c.Expires = DateTime.Now.AddDays(-10);
Response.Cookies.Add(c);
HttpCookie testCookie = new HttpCookie("Test");
testCookie.Value = "2";
testCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(testCookie);
}
I have updated your method.
public static void SetCookie(string key, string value, int dayExpires)
{
if (Request.Cookies[key] != null)
{
var c = HttpContext.Current.Request.Cookies[key];
c.Expires = DateTime.Now.AddDays(-10);
HttpContext.Current.Response.Cookies.Add(c);
}
HttpCookie encodedCookie = HttpSecureCookie.Encode(new HttpCookie(key, value));
encodedCookie.Expires = DateTime.Now.AddDays(dayExpires);
HttpContext.Current.Response.Cookies.Add(encodedCookie);
}
I'm building a web application using the default master template in VS2010 - very new to doing this. I'm also using the Login.aspx page, but instead of using the built in user validation, my user info is in a database table. So Following instructions I found, I'm doing something wery similar to this:
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
Boolean bauthenticated = false;
bauthenticated = isValidUser(Login1.UserName, Login1.Password);
if (bauthenticated)
{
e.Authenticated = true;
}
else
{
e.Authenticated = false;
}
}
The problem is that I put the method isValidUser in a .dll so it could be used elsewhere, and it is not receiving the password because the default behaivor is to blank it out. I even tried to set a string variable to Login1.Password, and pass the variable without success. I understand why this is happening, but can't find any info as to how to do this correctly. Do I need to put the user name and password into an object and pass that to my class constructor? I really don't want to connect to my database from every Login.aspx page I create to avoid sending the password over http.
Try to use the following code.
protected void LoginButton_Click(object sender, EventArgs e)
{
try
{
dtUserDetails = new DataTable();
if (UserRepositoryBL.ValidateUser(LoginUser.UserName.Trim(), LoginUser.Password.Trim(), out dtUserDetails))
{
AuthUser au = new AuthUser();
if (dtUserDetails.Rows.Count > 0)
{
DataRow DR = dtUserDetails.Rows[0];
au.UserID = Convert.ToInt32(DR["UserID"].ToString());
au.UserNo = DR["UserNo"].ToString();
au.UserName = DR["UserName"].ToString();
au.Password = DR["Password"].ToString();
}
string userData = au.ToString();
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // Version number
LoginUser.UserName.Trim(), // Username
DateTime.Now, // Issue date
DateTime.Now.AddMinutes(60), // Expiration date
false, // Persistent?
userData // User data
);
string eticket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie
(FormsAuthentication.FormsCookieName, eticket);
Response.Cookies.Add(cookie);
BasePage.ActivityLog("User Login", LoginUser.UserName.Trim(), true, Request.RawUrl);
string url = FormsAuthentication.GetRedirectUrl(LoginUser.UserName, false);
Response.Redirect(url);
// FormsAuthentication.RedirectFromLoginPage(LoginUser.UserName, false);
}
else
{
LoginUser.FailureText = "Your login attempt was not successful. Please try again.";
}
}
catch (Exception ex)
{
throw ex;
}
}
dtUserDetails is a out parameter which contains the user details like password,username,etc.. on successful login.datatable returns empty if invalid login.with in userData string all those information will be available.then u can retrieve those from any page using User Authenticated Ticket
I am trying to add a simple log in with Facebook button to my ASP.NET (C#) website. All I need is on the server side to retrieve the Facebook user's email address once they have logged in.
I was trying to use this example but it seems that the cookie "fbs_appid" is no longer used and instead there is one called "fbsr_appid".
How can I change the sample to use the different cookie? Alternately does anyone have a working example of retrieving the logged in Facebook user's email address.
I know there is an SDK I can use but I want to keep things simple. The above example would be perfect if it worked.
I managed to get the information needed using the fbsr cookie. I created the following class which does all of the work to confirm the user logged in with Facebook and then retrieves the user's details:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Serialization;
namespace HarlequinShared
{
public class FacebookLogin
{
protected static string _appId = null;
protected static string AppId
{
get
{
if (_appId == null)
_appId = ConfigurationManager.AppSettings["FacebookAppId"] ?? null;
return _appId;
}
}
protected static string _appSecret = null;
protected static string AppSecret
{
get
{
if (_appSecret == null)
_appSecret = ConfigurationManager.AppSettings["FacebookAppSecret"] ?? null;
return _appSecret;
}
}
public static FacebookUser CheckLogin()
{
string fbsr = HttpContext.Current.Request.Cookies["fbsr_" + AppId].Value;
int separator = fbsr.IndexOf(".");
if (separator == -1)
{
return null;
}
string encodedSig = fbsr.Substring(0, separator);
string payload = fbsr.Substring(separator + 1);
string sig = Base64Decode(encodedSig);
var serializer = new JavaScriptSerializer();
Dictionary<string, string> data = serializer.Deserialize<Dictionary<string, string>>(Base64Decode(payload));
if (data["algorithm"].ToUpper() != "HMAC-SHA256")
{
return null;
}
HMACSHA256 crypt = new HMACSHA256(Encoding.ASCII.GetBytes(AppSecret));
crypt.ComputeHash(Encoding.UTF8.GetBytes(payload));
string expectedSig = Encoding.UTF8.GetString(crypt.Hash);
if (sig != expectedSig)
{
return null;
}
string accessTokenResponse = FileGetContents("https://graph.facebook.com/oauth/access_token?client_id=" + AppId + "&redirect_uri=&client_secret=" + AppSecret + "&code=" + data["code"]);
NameValueCollection options = HttpUtility.ParseQueryString(accessTokenResponse);
string userResponse = FileGetContents("https://graph.facebook.com/me?access_token=" + options["access_token"]);
userResponse = Regex.Replace(userResponse, #"\\u([\dA-Fa-f]{4})", v => ((char)Convert.ToInt32(v.Groups[1].Value, 16)).ToString());
FacebookUser user = new FacebookUser();
Regex getValues = new Regex("(?<=\"email\":\")(.+?)(?=\")");
Match infoMatch = getValues.Match(userResponse);
user.Email = infoMatch.Value;
getValues = new Regex("(?<=\"first_name\":\")(.+?)(?=\")");
infoMatch = getValues.Match(userResponse);
user.FirstName = infoMatch.Value;
getValues = new Regex("(?<=\"last_name\":\")(.+?)(?=\")");
infoMatch = getValues.Match(userResponse);
user.LastName = infoMatch.Value;
return user;
}
protected static string FileGetContents(string url)
{
string result;
WebResponse response;
WebRequest request = HttpWebRequest.Create(url);
response = request.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
result = sr.ReadToEnd();
sr.Close();
}
return result;
}
protected static string Base64Decode(string input)
{
UTF8Encoding encoding = new UTF8Encoding();
string encoded = input.Replace("=", string.Empty).Replace('-', '+').Replace('_', '/');
var decoded = Convert.FromBase64String(encoded.PadRight(encoded.Length + (4 - encoded.Length % 4) % 4, '='));
var result = encoding.GetString(decoded);
return result;
}
}
public class FacebookUser
{
public string UID { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
And then I can use this in my login page:
FacebookUser user = FacebookLogin.CheckLogin();
if (user != null)
{
Response.Write("<p>" + user.Email);
Response.Write("<p>" + user.FirstName);
Response.Write("<p>" + user.LastName);
}
This is further explained here.
I believe that this method is secure and does the job as simply as possible. Please comment if there is any concerns.
This is not at all how you should be doing things, especially if you are trying to do it on the server side. You should not use the cookie.
The different sdks make things simpler, they (especially the official ones) stay up to date with facebook changes, unlike the example you provided that just stopped working when the cookie name was changed.
I'm not a C# developer, and never tried to use facebook from that environment so I can't tell you exactly how to do it with C#, but this is the main concept:
When you send the user for authentication (assumed Server Side Flow) you need to ask the user for permissions for anything but basic and public user information.
The email field is filed under "extended permission" and you need to ask for it specifically, something like this:
https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_url=YOUR_REDIRECT_URI&scope=email
(You can read more about permissions here)
Once the user authorized your app and granted you with the permissions, you can then ask for that data.
You can do it in the server side by asking for the "/me" graph path (or just the email: "me?fields=email").
You can also do it in the client side with the javascript sdk:
FB.api("/me", function(response) {
console.log(response);
});